Overview

GitHub allows you to create two kinds of webhooks.

  1. Organization Webhooks : These are webhooks that are created at the organization level. They are triggered by events that happen in the organization.
  2. Repository Webhooks : These are webhooks that are created at the repository level. They are triggered by events that happen in the repository.

GitHub API Quirks

Every GitHub REST API request for creating a webhook requires a minimum of either an organization or a combination of an organization and a repository. We provider two usefull hooks to help you get the organization and repository values for your users. See the below example on how to create a webhook for your user.

Creating a Webhook for a User using the SDK

  1. Get a list of your users Organizations with useGetGitHubOrganizations: This hook returns a list of organizations that the user is a part of based off of their GitHub access token. You can get this access token from your third party provider like Nango.

    create-GitHub.ts
    import { useGetGitHubOrganizations } from 'syncd-hooks';
    ...
    // Note: this is not async
    const { data, isLoading, error } = useGetGitHubOrganizations({
     // Get this from a third party like Nango
     accessToken,
    });
    ...
     <select
     name="org"
     id="org"
     >
       {isLoading && <option value="loading">Loading...</option>}
       <option value="default">Select an organization</option>
       {data?.map((org) => (
         <option key={org.id} value={org.login}>
           {org.login}
         </option>
       ))}
    </select>
    ...
    
  2. (OPTIONAL) Get a list of your users Repositories with useGetGitHubReposForOrganization: If you want to listen to a specific repository and not just the overarching Organization, this hook returns a list of repositories that the user is a part of based off of their GitHub access token and the organization they chose in the previous step. Don’t worry about the organization name not being selected yet, the repo hook handles the retry logic for you.

    create-GitHub.ts
    import { useGetGitHubReposForOrganization } from 'syncd-hooks;
    ...
    // Note: this is not async
    const { data, isLoading, error } = useGetGitHubReposForOrganization({
     accessToken: <access-token>,
     // get this from the useGetGitHubOrganizations hook
     organization: <organization-name>,
    });
    ...
     <select
     name="org"
     id="org"
     >
       {isLoading && <option value="loading">Loading...</option>}
       <option value="default">Select an organization</option>
       {data?.map((org) => (
         <option key={org.id} value={org.login}>
           {org.login}
         </option>
       ))}
    </select>
    ...
    
  3. Create a new webhook with all the information we gatherd from our user in the previous steps.

create-GitHub.ts
/**
 * We use JSDocs in the SDK to give you the best experience possible.
 * If you need to know what a function does, you can hover over it in your IDE.
 * If you need to know why you need a projectId for example, you can hover over it.
 */
const res = await syncdClient.providers.github.webhooks.create({
  accessToken: nango.github,
  callbackUrl: "<your-callback-url>",
  events: ["*"],
  // This can be anything you want... just for the dashboard so you can identify the webhook
  endpointName: `${
    user.id
  } - github - ${selectedOrg} - ${selectedRepo} - ${new Date().toISOString()}`,
  // Get this from the useGetGitHubReposForOrganization hook
  organization: selectedOrg,
  // Get this from the dashboard... it is safe to use on the client side
  projectId: "project_123",
  type: "org", // either org or repo
  endpoindDescription: "This is a test endpoint",
  // If you want to associate this with a user in your app
  // pass this through - but change the information to your user
  externalUser: {
    uniqueId: user.id,
    firstName: "Thomas",
    lastName: "Cistulli",
    metadata: {
      email: "tcistull@syncd.dev",
    },
  },
});

Updating a Webhook for a User

If you want you can update an existing webhook to listen to new events, update callback endpoints, and more. The resulting webhook will be under the same project and endpointId so you don’t need to worry about updating things in your database/storage.

This would look something like this:

await syncdClient.providers.github.webhooks.update({
  ...
})

Example Payloads

With each provider we have example payloads so you can test the webhook events. This can be useful for debugging and testing your application.

To import, each payload example starts with the provider name and then the event name. For example, figmaFileCommentEventExamplePayload. Use intelligent auto-complete in your IDE to find the payload you need.

Here is an example payload for branch_protection_rule:

  {
    "name": "branch_protection_rule",
    "description": "Activity related to a branch protection rule. For more information, see \"[About branch protection rules](https://docs.github.com/en/github/administering-a-repository/defining-the-mergeability-of-pull-requests/about-protected-branches#about-branch-protection-rules).\"",
    "actions": ["created", "deleted", "edited"],
    "properties": {
      "rule": {
        "type": "object",
        "description": "The branch protection rule. Includes a `name` and all the [branch protection settings](https://docs.github.com/en/github/administering-a-repository/defining-the-mergeability-of-pull-requests/about-protected-branches#about-branch-protection-settings) applied to branches that match the name. Binary settings are boolean. Multi-level configurations are one of `off`, `non_admins`, or `everyone`. Actor and build lists are arrays of strings."
      },
      "changes": {
        "type": "object",
        "description": "If the action was `edited`, the changes to the rule."
      },
      "repository": {
        "type": "object",
        "description": "The [`repository`](https://docs.github.com/en/rest/reference/repos#get-a-repository) where the event occurred."
      },
      "organization": {
        "type": "object",
        "description": "Webhook payloads contain the [`organization`](https://docs.github.com/en/rest/reference/orgs#get-an-organization) object when the webhook is configured for an organization or the event occurs from activity in a repository owned by an organization."
      },
      "sender": {
        "type": "object",
        "description": "The user that triggered the event."
      }
    },
    "examples": [
      {
        "action": "edited",
        "rule": {
          "id": 21796960,
          "repository_id": 259377789,
          "name": "production",
          "created_at": "2021-08-19T12:16:32.000-04:00",
          "updated_at": "2021-08-19T12:16:32.000-04:00",
          "pull_request_reviews_enforcement_level": "off",
          "required_approving_review_count": 1,
          "dismiss_stale_reviews_on_push": false,
          "require_code_owner_review": false,
          "authorized_dismissal_actors_only": false,
          "ignore_approvals_from_contributors": false,
          "required_status_checks": ["basic-CI"],
          "required_status_checks_enforcement_level": "non_admins",
          "strict_required_status_checks_policy": false,
          "signature_requirement_enforcement_level": "off",
          "linear_history_requirement_enforcement_level": "off",
          "admin_enforced": false,
          "allow_force_pushes_enforcement_level": "off",
          "allow_deletions_enforcement_level": "off",
          "merge_queue_enforcement_level": "off",
          "required_deployments_enforcement_level": "off",
          "required_conversation_resolution_level": "off",
          "authorized_actors_only": true,
          "authorized_actor_names": ["Codertocat"]
        },
        "changes": {
          "authorized_actors_only": { "from": false },
          "authorized_actor_names": { "from": [] }
        },
        "repository": {
          "id": 17273051,
          "node_id": "MDEwOlJlcG9zaXRvcnkxNzI3MzA1MQ==",
          "name": "octo-repo",
          "full_name": "octo-org/octo-repo",
          "private": true,
          "owner": {
            "login": "octo-org",
            "id": 6811672,
            "node_id": "MDEyOk9yZ2FuaXphdGlvbjY4MTE2NzI=",
            "avatar_url": "https://avatars.githubusercontent.com/u/6811672?v=4",
            "gravatar_id": "",
            "url": "https://api.github.com/users/octo-org",
            "html_url": "https://github.com/octo-org",
            "followers_url": "https://api.github.com/users/octo-org/followers",
            "following_url": "https://api.github.com/users/octo-org/following{/other_user}",
            "gists_url": "https://api.github.com/users/octo-org/gists{/gist_id}",
            "starred_url": "https://api.github.com/users/octo-org/starred{/owner}{/repo}",
            "subscriptions_url": "https://api.github.com/users/octo-org/subscriptions",
            "organizations_url": "https://api.github.com/users/octo-org/orgs",
            "repos_url": "https://api.github.com/users/octo-org/repos",
            "events_url": "https://api.github.com/users/octo-org/events{/privacy}",
            "received_events_url": "https://api.github.com/users/octo-org/received_events",
            "type": "Organization",
            "site_admin": false
          },
          "html_url": "https://github.com/octo-org/octo-repo",
          "description": "My first repo on GitHub!",
          "fork": false,
          "url": "https://api.github.com/repos/octo-org/octo-repo",
          "forks_url": "https://api.github.com/repos/octo-org/octo-repo/forks",
          "keys_url": "https://api.github.com/repos/octo-org/octo-repo/keys{/key_id}",
          "collaborators_url": "https://api.github.com/repos/octo-org/octo-repo/collaborators{/collaborator}",
          "teams_url": "https://api.github.com/repos/octo-org/octo-repo/teams",
          "hooks_url": "https://api.github.com/repos/octo-org/octo-repo/hooks",
          "issue_events_url": "https://api.github.com/repos/octo-org/octo-repo/issues/events{/number}",
          "events_url": "https://api.github.com/repos/octo-org/octo-repo/events",
          "assignees_url": "https://api.github.com/repos/octo-org/octo-repo/assignees{/user}",
          "branches_url": "https://api.github.com/repos/octo-org/octo-repo/branches{/branch}",
          "tags_url": "https://api.github.com/repos/octo-org/octo-repo/tags",
          "blobs_url": "https://api.github.com/repos/octo-org/octo-repo/git/blobs{/sha}",
          "git_tags_url": "https://api.github.com/repos/octo-org/octo-repo/git/tags{/sha}",
          "git_refs_url": "https://api.github.com/repos/octo-org/octo-repo/git/refs{/sha}",
          "trees_url": "https://api.github.com/repos/octo-org/octo-repo/git/trees{/sha}",
          "statuses_url": "https://api.github.com/repos/octo-org/octo-repo/statuses/{sha}",
          "languages_url": "https://api.github.com/repos/octo-org/octo-repo/languages",
          "stargazers_url": "https://api.github.com/repos/octo-org/octo-repo/stargazers",
          "contributors_url": "https://api.github.com/repos/octo-org/octo-repo/contributors",
          "subscribers_url": "https://api.github.com/repos/octo-org/octo-repo/subscribers",
          "subscription_url": "https://api.github.com/repos/octo-org/octo-repo/subscription",
          "commits_url": "https://api.github.com/repos/octo-org/octo-repo/commits{/sha}",
          "git_commits_url": "https://api.github.com/repos/octo-org/octo-repo/git/commits{/sha}",
          "comments_url": "https://api.github.com/repos/octo-org/octo-repo/comments{/number}",
          "issue_comment_url": "https://api.github.com/repos/octo-org/octo-repo/issues/comments{/number}",
          "contents_url": "https://api.github.com/repos/octo-org/octo-repo/contents/{+path}",
          "compare_url": "https://api.github.com/repos/octo-org/octo-repo/compare/{base}...{head}",
          "merges_url": "https://api.github.com/repos/octo-org/octo-repo/merges",
          "archive_url": "https://api.github.com/repos/octo-org/octo-repo/{archive_format}{/ref}",
          "downloads_url": "https://api.github.com/repos/octo-org/octo-repo/downloads",
          "issues_url": "https://api.github.com/repos/octo-org/octo-repo/issues{/number}",
          "pulls_url": "https://api.github.com/repos/octo-org/octo-repo/pulls{/number}",
          "milestones_url": "https://api.github.com/repos/octo-org/octo-repo/milestones{/number}",
          "notifications_url": "https://api.github.com/repos/octo-org/octo-repo/notifications{?since,all,participating}",
          "labels_url": "https://api.github.com/repos/octo-org/octo-repo/labels{/name}",
          "releases_url": "https://api.github.com/repos/octo-org/octo-repo/releases{/id}",
          "deployments_url": "https://api.github.com/repos/octo-org/octo-repo/deployments",
          "created_at": "2014-02-28T02:42:51Z",
          "updated_at": "2021-03-11T14:54:13Z",
          "pushed_at": "2021-03-11T14:54:10Z",
          "git_url": "git://github.com/octo-org/octo-repo.git",
          "ssh_url": "org-6811672@github.com:octo-org/octo-repo.git",
          "clone_url": "https://github.com/octo-org/octo-repo.git",
          "svn_url": "https://github.com/octo-org/octo-repo",
          "homepage": "",
          "size": 300,
          "stargazers_count": 0,
          "watchers_count": 0,
          "language": "JavaScript",
          "has_issues": true,
          "has_projects": false,
          "has_downloads": true,
          "has_wiki": false,
          "has_pages": true,
          "forks_count": 0,
          "mirror_url": null,
          "archived": false,
          "disabled": false,
          "open_issues_count": 39,
          "license": null,
          "forks": 0,
          "open_issues": 39,
          "watchers": 0,
          "default_branch": "main"
        },
        "organization": {
          "login": "octo-org",
          "id": 6811672,
          "node_id": "MDEyOk9yZ2FuaXphdGlvbjY4MTE2NzI=",
          "url": "https://api.github.com/orgs/octo-org",
          "repos_url": "https://api.github.com/orgs/octo-org/repos",
          "events_url": "https://api.github.com/orgs/octo-org/events",
          "hooks_url": "https://api.github.com/orgs/octo-org/hooks",
          "issues_url": "https://api.github.com/orgs/octo-org/issues",
          "members_url": "https://api.github.com/orgs/octo-org/members{/member}",
          "public_members_url": "https://api.github.com/orgs/octo-org/public_members{/member}",
          "avatar_url": "https://avatars.githubusercontent.com/u/6811672?v=4",
          "description": "Working better together!"
        },
        "sender": {
          "login": "Codertocat",
          "id": 21031067,
          "node_id": "MDQ6VXNlcjIxMDMxMDY3",
          "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4",
          "gravatar_id": "",
          "url": "https://api.github.com/users/Codertocat",
          "html_url": "https://github.com/Codertocat",
          "followers_url": "https://api.github.com/users/Codertocat/followers",
          "following_url": "https://api.github.com/users/Codertocat/following{/other_user}",
          "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}",
          "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions",
          "organizations_url": "https://api.github.com/users/Codertocat/orgs",
          "repos_url": "https://api.github.com/users/Codertocat/repos",
          "events_url": "https://api.github.com/users/Codertocat/events{/privacy}",
          "received_events_url": "https://api.github.com/users/Codertocat/received_events",
          "type": "User",
          "site_admin": false
        }
      },
      ...
    ]
  },

Types

In the background for GitHub we use Octoit for all our types. You can find a list of all the types and requests in the GitHub provider here: GitHub Types

Seeing logs and webhook bodies

To view logs or webhook bodies for debugging, you can use the Dashboard. If you messed up the callback URL endpoint you can change it here.

You can go to the dashboard here: Syncd.