machine-readable, always up-to-date GitHub Webhooks specifications
Download the latest specification at octokit.github.io/webhooks/payload-examples/api.github.com/index.json
Example webhook definition
{
"name": "issues",
"actions": [
"opened",
"edited",
"deleted",
"transferred",
"closed",
"reopened",
"assigned",
"unassigned",
"labeled",
"unlabeled",
"milestoned",
"demilestoned"
],
"examples": [
{
"action": "edited",
"issue": {
"url": "https://api.github.com/repos/Codertocat/Hello-World/issues/2",
"repository_url": "https://api.github.com/repos/Codertocat/Hello-World",
"labels_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/2/labels{/name}",
"comments_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/2/comments",
"events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/2/events",
"html_url": "https://github.com/Codertocat/Hello-World/issues/2",
"id": 327883527,
"node_id": "MDU6SXNzdWUzMjc4ODM1Mjc=",
"number": 2,
"title": "Spelling error in the README file",
"user": {
"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
},
"labels": [
{
"id": 949737505,
"node_id": "MDU6TGFiZWw5NDk3Mzc1MDU=",
"url": "https://api.github.com/repos/Codertocat/Hello-World/labels/bug",
"name": "bug",
"color": "d73a4a",
"default": true
}
],
"state": "open",
"locked": false,
"assignee": null,
"assignees": [
{
"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
}
],
"milestone": null,
"comments": 0,
"created_at": "2018-05-30T20:18:32Z",
"updated_at": "2018-05-30T20:18:32Z",
"closed_at": null,
"author_association": "OWNER",
"body": "It looks like you accidently spelled 'commit' with two 't's."
},
"changes": {},
"repository": {
"id": 135493233,
"node_id": "MDEwOlJlcG9zaXRvcnkxMzU0OTMyMzM=",
"name": "Hello-World",
"full_name": "Codertocat/Hello-World",
"owner": {
"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
},
"private": false,
"html_url": "https://github.com/Codertocat/Hello-World",
"description": null,
"fork": false,
"url": "https://api.github.com/repos/Codertocat/Hello-World",
"forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks",
"keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams",
"hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks",
"issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}",
"events_url": "https://api.github.com/repos/Codertocat/Hello-World/events",
"assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}",
"branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}",
"tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags",
"blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}",
"languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages",
"stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers",
"contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors",
"subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers",
"subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription",
"commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}",
"contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}",
"compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges",
"archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads",
"issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}",
"pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}",
"milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}",
"notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}",
"releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}",
"deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments",
"created_at": "2018-05-30T20:18:04Z",
"updated_at": "2018-05-30T20:18:10Z",
"pushed_at": "2018-05-30T20:18:30Z",
"git_url": "git://github.com/Codertocat/Hello-World.git",
"ssh_url": "git@github.com:Codertocat/Hello-World.git",
"clone_url": "https://github.com/Codertocat/Hello-World.git",
"svn_url": "https://github.com/Codertocat/Hello-World",
"homepage": null,
"size": 0,
"stargazers_count": 0,
"watchers_count": 0,
"language": null,
"has_issues": true,
"has_projects": true,
"has_downloads": true,
"has_wiki": true,
"has_pages": true,
"forks_count": 0,
"mirror_url": null,
"archived": false,
"open_issues_count": 2,
"license": null,
"forks": 0,
"open_issues": 2,
"watchers": 0,
"default_branch": "master"
},
"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
}
}
]
}
You can download the latest index.json
and schema.json
files from
unpkg
To get an array of all webhook definition objects, require the @octokit/webhooks-examples
package.
// Use Node.js require:
const WEBHOOKS = require("@octokit/webhooks-examples/api.github.com/index.json");
// Or ESM/TypeScript import:
import WEBHOOKS from "@octokit/webhooks-examples/api.github.com/index.json";
To get the JSON schema for webhook payloads, require the @octokit/webhooks-schemas
package.
// Use Node.js require:
const SCHEMA = require("@octokit/webhooks-schemas");
// Or ESM/TypeScript import:
import SCHEMA from "@octokit/webhooks-schemas";
When running in strict
mode, ajv
will throw an "unknown keyword" error if it
encounters any keywords that have not been defined.
This schema currently uses custom keywords provided by ajv-formats
, along with
the custom keyword tsAdditionalProperties
.
Here is an example of how you can set this up:
import type { WebhookEvent } from "@octokit/webhooks-types";
import * as githubWebhookSchema from "@octokit/webhooks-schemas";
import Ajv from "ajv";
import addFormats from "ajv-formats";
const ajv = new Ajv({ strict: true });
addFormats(ajv);
ajv.addKeyword("tsAdditionalProperties");
const validate = ajv.compile<WebhookEvent>(githubWebhookSchema);
This package ships with types for the webhook events generated from the respective json schemas, which you can use like so:
import { WebhookEvent, IssuesOpenedEvent } from "@octokit/webhooks-types";
const handleWebhookEvent = (event: WebhookEvent) => {
if ("action" in event && event.action === "completed") {
console.log(`${event.sender.login} completed something!`);
}
};
const handleIssuesOpenedEvent = (event: IssuesOpenedEvent) => {
console.log(
`${event.sender.login} opened "${event.issue.title}" on ${event.repository.full_name}`,
);
};
strictNullChecks
option enabled in your tsconfig
. If you don't have this option enabled, there's the possibility that you get never
as the inferred type in some use cases. See #395 for details.
This package updates itself using a daily cronjob running on GitHub Actions. The
index.json
file is generated by
bin/octokit-webhooks.mts
. Run
npm run octokit-webhooks -- --usage
for instructions. After the update is
complete, run npm run build:webhooks
and npm run build:schema
to update
index.json
and schema.json
files.
The update script is scraping GitHub’s Webhooks Event Types & Payloads documentation page and extracts the meta information using cheerio.
For simpler local testing and tracking of changes all loaded pages are cached in
the cache/
folder.
- octokit/graphql-schema – GitHub’s GraphQL Schema with validation
- octokit/openapi – GitHub REST API route specifications
- octokit/app-permissions – GitHub App permission specifications
What conventions are the schemas of this repository following?
This section offers the origin of these conventions and the reasoning behind them.
The JSON schema for a committer (committer.schema.json
) has a property "email"
, which originally was using the default email
format offered by ajv-format
.
What we noticed is the Regular Expression used by ajv-format for emails
is not accepting emails containing [
or ]
characters (i.e. 41898282+github-actions
[bot
]@users.noreply.github.com
).
After considering the option of applying our pattern by extending ajv-format
Regular Expression for emails, we considered not to do that because:
- It is an excellent way to cause more trouble (the size of this Regular Expression alone is enormous even before you think about all the sub-patterns).
- The moment we decide to use our Regular Expression, we are committing to maintaining it, so if someone else comes along and presents another email that is considered invalid by this pattern but works with git, we will need to safely modify the pattern again to cover that in addition to what we currently consider valid.
- If we want to be accurate, we should follow a spec to decide what is valid and what is not. Since GitHub is not following a spec for emails (and it will not change any time soon), to be validating emails with our RegEx would not be correct either.
You can read more details and the discussion behind in this resources: