Diffinery
Reference: Webhook events
Docs / Reference

Webhook events

Reference of every webhook event and action Diffinery sends, with the exact payload fields and example payloads.

This page lists every event a Diffinery webhook can deliver, the actions of each event, and the exact payload fields, with an example for each. Use it to write or adapt a webhook receiver.

There are two kinds of payload. push, pull_request, pull_request_review, issues, issue_comment and workflow_run have small Diffinery payloads with ids and links. release, status, deployment, deployment_status, deployment_review, repository_dispatch and workflow_dispatch have GitHub's payload structure.

Common structure

Every delivery is an HTTPS POST with a compact JSON body and these headers:

HeaderValue
X-GitHub-EventThe event name from this page, for example pull_request
X-Diffinery-EventThe same event name
X-GitHub-DeliveryA unique delivery id (32 hexadecimal characters). Retries of a failed delivery keep the same id.
X-Hub-Signature-256sha256= and the hex HMAC-SHA256 of the body with the webhook's secret
User-AgentDiffinery-Hookshot/1
Content-Typeapplication/json

Every payload contains:

FieldTypeDescription
actionstringThe action, for events that have one. Absent for push, status and workflow_dispatch.
atstringISO 8601 time the event happened. In the small payloads and in workflow_dispatch; release, status, deployment, deployment_status, deployment_review and repository_dispatch have no at (use their own created_at and updated_at fields).
repositoryobjectThe repository (see below)
senderobjectThe account that caused the event (see below)

A delivery that fails with a network error, a timeout (10 seconds), 408, 429 or a 5xx status is sent again up to five times, 1 minute, 5 minutes, 30 minutes, 1 hour and 3 hours after each failed attempt, with the same id and body. See Webhooks.

Deliveries to an app's webhook also carry installation (id and node_id): the installation they came through. Apps also get installation events (created, deleted, suspend, unsuspend, new_permissions_accepted, with the installation object and, when created, its repositories) and installation_repositories events (added, removed, with repositories_added and repositories_removed), shaped like GitHub's.

The other top-level fields depend on the event. In the small payloads, Diffinery-specific fields use camelCase (headRef, headSha, baseRef, commentId, runId), and GitHub-style objects (pull_request, issue) are added where GitHub has them.

The repository object

repository is GitHub's full repository object, the same shape GET /api/v3/repos/{owner}/{repo} returns, so receivers that read repository.private, repository.visibility, repository.owner or repository.default_branch work unchanged:

{
  "id": 6628874657374920,
  "node_id": "UmVwb3NpdG9yeTo1YzBlOGI3ZjJkMWE0ZTNiOWY2YTdjOGQ5ZTBmMWEyYg",
  "name": "website",
  "full_name": "acme/website",
  "private": false,
  "owner": {
    "login": "acme",
    "id": 3915577218345164,
    "node_id": "T3JnYW5pemF0aW9uOjdlNmQ1YzRiM2EyZjFlMGQ5YzhiN2E2ZjVlNGQzYzJi",
    "avatar_url": "https://diffinery.com/api/avatars/acme",
    "gravatar_id": "",
    "url": "https://diffinery.com/api/v3/users/acme",
    "html_url": "https://diffinery.com/acme",
    "repos_url": "https://diffinery.com/api/v3/users/acme/repos",
    "organizations_url": "https://diffinery.com/api/v3/users/acme/orgs",
    "type": "Organization",
    "user_view_type": "public",
    "site_admin": false
  },
  "html_url": "https://diffinery.com/acme/website",
  "description": "The acme.example website",
  "fork": false,
  "url": "https://diffinery.com/api/v3/repos/acme/website",
  "archive_url": "https://diffinery.com/api/v3/repos/acme/website/{archive_format}{/ref}",
  "branches_url": "https://diffinery.com/api/v3/repos/acme/website/branches{/branch}",
  "commits_url": "https://diffinery.com/api/v3/repos/acme/website/commits{/sha}",
  "compare_url": "https://diffinery.com/api/v3/repos/acme/website/compare/{base}...{head}",
  "contents_url": "https://diffinery.com/api/v3/repos/acme/website/contents/{+path}",
  "deployments_url": "https://diffinery.com/api/v3/repos/acme/website/deployments",
  "git_refs_url": "https://diffinery.com/api/v3/repos/acme/website/git/refs{/sha}",
  "hooks_url": "https://diffinery.com/api/v3/repos/acme/website/hooks",
  "issues_url": "https://diffinery.com/api/v3/repos/acme/website/issues{/number}",
  "labels_url": "https://diffinery.com/api/v3/repos/acme/website/labels{/name}",
  "languages_url": "https://diffinery.com/api/v3/repos/acme/website/languages",
  "merges_url": "https://diffinery.com/api/v3/repos/acme/website/merges",
  "pulls_url": "https://diffinery.com/api/v3/repos/acme/website/pulls{/number}",
  "releases_url": "https://diffinery.com/api/v3/repos/acme/website/releases{/id}",
  "statuses_url": "https://diffinery.com/api/v3/repos/acme/website/statuses/{sha}",
  "tags_url": "https://diffinery.com/api/v3/repos/acme/website/tags",
  "git_url": "https://diffinery.com/acme/website.git",
  "ssh_url": "https://diffinery.com/acme/website.git",
  "clone_url": "https://diffinery.com/acme/website.git",
  "svn_url": "https://diffinery.com/acme/website",
  "homepage": null,
  "size": 0,
  "stargazers_count": 0,
  "watchers_count": 0,
  "language": null,
  "has_issues": true,
  "has_projects": false,
  "has_downloads": true,
  "has_wiki": false,
  "has_pages": false,
  "has_discussions": false,
  "forks_count": 0,
  "mirror_url": null,
  "archived": false,
  "disabled": false,
  "open_issues_count": 0,
  "license": null,
  "allow_forking": false,
  "is_template": false,
  "web_commit_signoff_required": false,
  "topics": [
    "website"
  ],
  "visibility": "public",
  "forks": 0,
  "open_issues": 0,
  "watchers": 0,
  "default_branch": "main",
  "created_at": "2026-03-02T14:20:11Z",
  "updated_at": "2026-10-12T16:03:40Z",
  "pushed_at": "2026-10-13T09:30:00Z"
}
FieldDescription
idThe repository's numeric id, the same number the REST API returns. It does not change when the repository is renamed or transferred, so use it rather than full_name to recognize a repository.
node_idAn opaque string id for the repository, as in the REST API
name, full_nameRepository name, and owner/name
privatetrue for a private repository, false for a public one
visibilitypublic or private: the repository's real visibility when the event is delivered
ownerThe owning account as a user object: login, numeric id, node_id, type (User or Organization), html_url, avatar_url and API URLs
descriptionThe repository's description, or null
default_branchThe default branch, for example main
archivedtrue when the repository is archived
topicsThe repository's topics
html_url, clone_url, git_url, ssh_url, svn_urlWeb address and clone URLs (all HTTPS: Diffinery has no SSH or git protocol access)
url and the *_url templatesREST API addresses, as on GitHub
created_at, updated_at, pushed_atISO 8601 times

Fields GitHub fills with counts (size, stargazers_count, watchers_count, forks_count, open_issues_count and their short forms) are always 0 in webhook payloads, fork and allow_forking are false, and there is no permissions object. If the repository was deleted before the delivery was sent, repository falls back to a minimal object (id, node_id, name, full_name, owner.login, html_url, clone_url) with "private": true and "visibility": "private".

To keep them short, the examples on this page show repository with only a few of its fields.

The sender object

A person:

{ "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90", "login": "diffy", "type": "User" }

An agent, with the login of the person or organization it acts for:

{ "id": "f0e1d2c3b4a5968778695a4b3c2d1e0f", "login": "review-bot", "type": "Bot", "on_behalf_of": "diffy" }
FieldDescription
idAccount id (32 hexadecimal characters, unlike the numeric repository.id), or an id such as system:merge-queue for Diffinery's own actors
loginLogin of the account
typeUser for people, Bot for agents and for Diffinery's own actors
on_behalf_ofOnly for agents: the login of the agent's owner

Diffinery's own actors appear as Bot senders: diffinery-merge-queue[bot] (merge queue), diffinery-scheduler (scheduled workflow runs), and diffinery-actions[bot] with an id such as job:2f1e0d9c8b7a69584736251403f2e1d0 for what a workflow job does with its GITHUB_TOKEN (opening an issue, posting a status, creating a release, ...).

push

Sent when a branch or tag is created, moved or deleted. This includes git push, commits from the web editor, the contents API, the Git data API (git/refs) and MCP, merges of pull requests (a push to the base branch), updating a pull request's branch, the merge queue's own gh-readonly-queue/... branches, and tags created by publishing a release.

FieldTypeDescription
refstringThe full ref, for example refs/heads/main or refs/tags/v1.2.0
beforestringCommit SHA the ref pointed to before; 40 zeros when the ref was created
afterstringCommit SHA the ref points to now; 40 zeros when the ref was deleted
forcedbooleantrue when the update was not a fast-forward
createdbooleantrue when the ref was created
deletedbooleantrue when the ref was deleted
atstringTime of the push

There is no action, no commits list, no head_commit and no pusher; sender is the pusher. Use GET /api/v3/repos/{owner}/{repo}/compare/{before}...{after} to get the commits.

{
  "ref": "refs/heads/main",
  "before": "6f1e2d3c4b5a69788796a5b4c3d2e1f0a9b8c7d6",
  "after": "9c4d8e2f1a0b3c5d7e9f2a4b6c8d0e1f3a5b7c9d",
  "forced": false,
  "created": false,
  "deleted": false,
  "at": "2026-10-13T09:30:00.000Z",
  "repository": { "id": 6628874657374920, "name": "website", "full_name": "acme/website", "private": false, "visibility": "public", "html_url": "https://diffinery.com/acme/website" },
  "sender": { "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90", "login": "diffy", "type": "User" }
}

A new branch:

{
  "ref": "refs/heads/fix-typo",
  "before": "0000000000000000000000000000000000000000",
  "after": "2b7e9d4c1a3f5e7d9b0c2a4e6f8d0b1c3e5a7f9d",
  "forced": false,
  "created": true,
  "deleted": false,
  "at": "2026-10-13T09:31:12.000Z",
  "repository": { "id": 6628874657374920, "name": "website", "full_name": "acme/website", "private": false, "visibility": "public", "html_url": "https://diffinery.com/acme/website" },
  "sender": { "id": "f0e1d2c3b4a5968778695a4b3c2d1e0f", "login": "review-bot", "type": "Bot", "on_behalf_of": "diffy" }
}

pull_request

Sent for pull request activity.

ActionSent whenSender
openedA pull request is createdIts author
synchronizeThe head branch moved to a new commitThe pusher
editedThe title or description changed (web UI, PATCH /pulls/{number}, or the update_issue MCP tool). Not sent when the new text equals the old, or for label changes.Who edited it
ready_for_reviewA draft is marked ready for review (Ready for review, diffinery pr ready, or PATCH with "draft": false)Who marked it
converted_to_draftAn open pull request is turned back into a draft (diffinery pr ready --undo, or PATCH with "draft": true). The pull request's timeline records this as convert_to_draft, GitHub's timeline name; the webhook action is converted_to_draft, as on GitHub.Who converted it
closedThe pull request is closed without merging (pull_request.merged is false), or is merged (pull_request.merged is true)Who closed or merged it; for a merge by the merge queue, who added it to the queue
reopenedA closed pull request is reopenedWho reopened it
assignedOne or more people are newly assignedWho assigned them
review_requestedOne or more reviewers are newly requestedWho requested the review
enqueuedThe pull request was added to a merge queueWho added it
dequeuedThe pull request was removed from the merge queue without merging: by a person, or because its merge group failed, conflicted or timed outWho removed it, or diffinery-merge-queue[bot]
auto_merge_enabledAuto-merge was turned on, or its method changedWho turned it on
auto_merge_disabledAuto-merge was turned off: by someone, by a push from someone else, by converting the pull request to a draft, or by itself because it can't go throughWho turned it off, the pusher, or diffinery-auto-merge[bot]
FieldTypeDescription
actionstringOne of the actions above
numbernumberThe pull request number
headRefstringHead branch name
headShastringHead commit SHA
baseRefstringBase branch name
atstringTime of the event
assigneesarray of stringsOnly for assigned: logins newly assigned
reviewersarray of stringsOnly for review_requested: logins newly requested
pull_request.numbernumberThe pull request number
pull_request.html_urlstringWeb address of the pull request
pull_request.head.refstringHead branch name
pull_request.head.shastringHead commit SHA
pull_request.base.refstringBase branch name
pull_request.mergedbooleantrue only when the pull request was merged (action closed)

The payload does not include the title, body, author, labels or draft state, and edited has no changes object with the old values. Fetch the current values with GET /api/v3/repos/{owner}/{repo}/pulls/{number}.

Opened:

{
  "action": "opened",
  "number": 42,
  "headRef": "fix-typo",
  "headSha": "2b7e9d4c1a3f5e7d9b0c2a4e6f8d0b1c3e5a7f9d",
  "baseRef": "main",
  "at": "2026-10-13T09:32:40.000Z",
  "repository": { "id": 6628874657374920, "name": "website", "full_name": "acme/website", "private": false, "visibility": "public", "html_url": "https://diffinery.com/acme/website" },
  "sender": { "id": "f0e1d2c3b4a5968778695a4b3c2d1e0f", "login": "review-bot", "type": "Bot", "on_behalf_of": "diffy" },
  "pull_request": {
    "number": 42,
    "html_url": "https://diffinery.com/acme/website/pull/42",
    "head": { "ref": "fix-typo", "sha": "2b7e9d4c1a3f5e7d9b0c2a4e6f8d0b1c3e5a7f9d" },
    "base": { "ref": "main" },
    "merged": false
  }
}

Merged (delivered as closed):

{
  "action": "closed",
  "number": 42,
  "headRef": "fix-typo",
  "headSha": "2b7e9d4c1a3f5e7d9b0c2a4e6f8d0b1c3e5a7f9d",
  "baseRef": "main",
  "at": "2026-10-13T10:05:11.000Z",
  "repository": { "id": 6628874657374920, "name": "website", "full_name": "acme/website", "private": false, "visibility": "public", "html_url": "https://diffinery.com/acme/website" },
  "sender": { "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90", "login": "diffy", "type": "User" },
  "pull_request": {
    "number": 42,
    "html_url": "https://diffinery.com/acme/website/pull/42",
    "head": { "ref": "fix-typo", "sha": "2b7e9d4c1a3f5e7d9b0c2a4e6f8d0b1c3e5a7f9d" },
    "base": { "ref": "main" },
    "merged": true
  }
}

A merge also sends a push event for the base branch.

Review requested:

{
  "action": "review_requested",
  "number": 42,
  "headRef": "fix-typo",
  "headSha": "2b7e9d4c1a3f5e7d9b0c2a4e6f8d0b1c3e5a7f9d",
  "baseRef": "main",
  "at": "2026-10-13T09:33:05.000Z",
  "reviewers": ["diffy"],
  "repository": { "id": 6628874657374920, "name": "website", "full_name": "acme/website", "private": false, "visibility": "public", "html_url": "https://diffinery.com/acme/website" },
  "sender": { "id": "f0e1d2c3b4a5968778695a4b3c2d1e0f", "login": "review-bot", "type": "Bot", "on_behalf_of": "diffy" },
  "pull_request": {
    "number": 42,
    "html_url": "https://diffinery.com/acme/website/pull/42",
    "head": { "ref": "fix-typo", "sha": "2b7e9d4c1a3f5e7d9b0c2a4e6f8d0b1c3e5a7f9d" },
    "base": { "ref": "main" },
    "merged": false
  }
}

The assigned payload has the same shape with "assignees": ["diffy"] instead of reviewers.

Ready for review (edited and converted_to_draft look the same apart from action):

{
  "action": "ready_for_review",
  "number": 42,
  "headRef": "fix-typo",
  "headSha": "2b7e9d4c1a3f5e7d9b0c2a4e6f8d0b1c3e5a7f9d",
  "baseRef": "main",
  "at": "2026-10-13T09:45:10.000Z",
  "repository": { "id": 6628874657374920, "name": "website", "full_name": "acme/website", "private": false, "visibility": "public", "html_url": "https://diffinery.com/acme/website" },
  "sender": { "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90", "login": "diffy", "type": "User" },
  "pull_request": {
    "number": 42,
    "html_url": "https://diffinery.com/acme/website/pull/42",
    "head": { "ref": "fix-typo", "sha": "2b7e9d4c1a3f5e7d9b0c2a4e6f8d0b1c3e5a7f9d" },
    "base": { "ref": "main" },
    "merged": false
  }
}

pull_request_review

Sent when someone submits a review on a pull request.

ActionSent when
submittedA review is submitted (approve, request changes, or comment)
FieldTypeDescription
actionstringsubmitted
numbernumberThe pull request number
statestringAPPROVED, CHANGES_REQUESTED or COMMENTED (uppercase)
atstringTime of the review

There is no review or pull_request object, and the review body is not included. Get the review with GET /api/v3/repos/{owner}/{repo}/pulls/{number}/reviews. GitHub sends review.state in lowercase; Diffinery's top-level state is uppercase.

{
  "action": "submitted",
  "number": 42,
  "state": "APPROVED",
  "at": "2026-10-13T09:50:00.000Z",
  "repository": { "id": 6628874657374920, "name": "website", "full_name": "acme/website", "private": false, "visibility": "public", "html_url": "https://diffinery.com/acme/website" },
  "sender": { "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90", "login": "diffy", "type": "User" }
}

issues

Sent for issue activity. Pull requests send pull_request events instead.

ActionSent when
openedAn issue is created
editedThe title or body changed (web UI, PATCH /issues/{number}, or the update_issue MCP tool)
closedAn issue is closed, including by a merged pull request that says fixes #12
reopenedA closed issue is reopened
assignedOne or more people are newly assigned

Saving the same title and body again sends nothing, and changing labels sends no event. edited has no changes object with the old values; read the issue with the REST API for the current ones.

FieldTypeDescription
actionstringOne of the actions above
numbernumberThe issue number
atstringTime of the event
assigneesarray of stringsOnly for assigned: logins newly assigned
issue.numbernumberThe issue number
issue.html_urlstringWeb address of the issue
{
  "action": "opened",
  "number": 12,
  "at": "2026-10-13T08:15:00.000Z",
  "repository": { "id": 6628874657374920, "name": "website", "full_name": "acme/website", "private": false, "visibility": "public", "html_url": "https://diffinery.com/acme/website" },
  "sender": { "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90", "login": "diffy", "type": "User" },
  "issue": { "number": 12, "html_url": "https://diffinery.com/acme/website/issues/12" }
}
{
  "action": "assigned",
  "number": 12,
  "at": "2026-10-13T08:20:00.000Z",
  "assignees": ["review-bot"],
  "repository": { "id": 6628874657374920, "name": "website", "full_name": "acme/website", "private": false, "visibility": "public", "html_url": "https://diffinery.com/acme/website" },
  "sender": { "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90", "login": "diffy", "type": "User" },
  "issue": { "number": 12, "html_url": "https://diffinery.com/acme/website/issues/12" }
}

issue_comment

Sent when a comment is added to an issue or to a pull request's conversation. Line comments inside a review do not send this event.

ActionSent when
createdA comment is added
FieldTypeDescription
actionstringcreated
numbernumberThe issue or pull request number
commentIdstringThe comment's id
atstringTime of the comment
issue.numbernumberThe issue or pull request number
issue.html_urlstringhttps://diffinery.com/{owner}/{repo}/issues/{number}; for a pull request this address redirects to the pull request

The comment body is not included. Read it with GET /api/v3/repos/{owner}/{repo}/issues/{number}/comments and find the entry whose id equals commentId. Comments posted by diffinery-merge-queue[bot] (for example when a pull request leaves the queue) also send this event.

{
  "action": "created",
  "number": 42,
  "commentId": "3e2d1c0b9a8f4e7d6c5b4a3f2e1d0c9b",
  "at": "2026-10-13T09:40:00.000Z",
  "repository": { "id": 6628874657374920, "name": "website", "full_name": "acme/website", "private": false, "visibility": "public", "html_url": "https://diffinery.com/acme/website" },
  "sender": { "id": "f0e1d2c3b4a5968778695a4b3c2d1e0f", "login": "review-bot", "type": "Bot", "on_behalf_of": "diffy" },
  "issue": { "number": 42, "html_url": "https://diffinery.com/acme/website/issues/42" }
}

workflow_run

Sent when a Diffinery Actions workflow run is created and when it finishes.

ActionSent when
requestedA run is created, for any trigger (push, pull request, schedule, manual dispatch, merge queue, workflow run, release, repository dispatch)
completedThe run finished
FieldTypeDescription
actionstringrequested or completed
runIdstringThe run id. The run is at https://diffinery.com/{owner}/{repo}/actions/runs/{runId} and in the API at GET /api/v3/repos/{owner}/{repo}/actions/runs/{runId}.
conclusionstringOnly for completed: success, failure or cancelled
atstringTime of the event

The sender is the account that triggered the run; for scheduled runs it is diffinery-scheduler. The sender of this event has no on_behalf_of field, even for agents. There is no workflow_run object: fetch the run for its workflow, branch and SHA.

{
  "action": "completed",
  "runId": "8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a",
  "conclusion": "success",
  "at": "2026-10-13T09:36:21.000Z",
  "repository": { "id": 6628874657374920, "name": "website", "full_name": "acme/website", "private": false, "visibility": "public", "html_url": "https://diffinery.com/acme/website" },
  "sender": { "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90", "login": "diffy", "type": "User" }
}

release

Sent for release activity, including activity on drafts. See Releases.

ActionSent when
createdA release is created, as a draft or published
publishedA release is published: created as published, or a draft is published
releasedA full (not pre-release) release is published, or a published pre-release becomes a full release
prereleasedA pre-release is published, or a published release becomes a pre-release
editedA release is edited without publishing or unpublishing it (also for drafts)
unpublishedA published release is turned back into a draft
deletedA release is deleted

One change can send several deliveries, in this order. Creating a published release sends created, published and then released (or prereleased). Publishing a draft sends published and released (or prereleased). Editing a published release's pre-release flag sends edited and released (or prereleased). Uploading, renaming or deleting assets sends nothing.

FieldTypeDescription
actionstringOne of the actions above
releaseobjectThe release, exactly as GET /api/v3/repos/{owner}/{repo}/releases/{id} returns it, including assets, upload_url, author and Diffinery's latest
repositoryobjectThe repository
senderobjectWho made the change

The release.reactions counts in webhook payloads are always zero; read reactions with the REST API.

{
  "action": "published",
  "release": {
    "url": "https://diffinery.com/api/v3/repos/acme/website/releases/7",
    "assets_url": "https://diffinery.com/api/v3/repos/acme/website/releases/7/assets",
    "upload_url": "https://diffinery.com/api/uploads/repos/acme/website/releases/7/assets{?name,label}",
    "html_url": "https://diffinery.com/acme/website/releases/tag/v1.2.0",
    "id": 7,
    "author": {
      "login": "diffy",
      "id": 7220136606554077,
      "node_id": "VXNlcjphMWIyYzNkNGU1ZjYwNzE4MjkzYTRiNWM2ZDdlOGY5MA",
      "avatar_url": "https://diffinery.com/api/avatars/diffy",
      "gravatar_id": "",
      "url": "https://diffinery.com/api/v3/users/diffy",
      "html_url": "https://diffinery.com/diffy",
      "repos_url": "https://diffinery.com/api/v3/users/diffy/repos",
      "organizations_url": "https://diffinery.com/api/v3/users/diffy/orgs",
      "type": "User",
      "user_view_type": "public",
      "site_admin": false
    },
    "node_id": "UmVsZWFzZTo3",
    "tag_name": "v1.2.0",
    "target_commitish": "main",
    "name": "Website 1.2.0",
    "draft": false,
    "immutable": false,
    "prerelease": false,
    "created_at": "2026-10-13T10:00:00Z",
    "updated_at": "2026-10-13T10:00:00Z",
    "published_at": "2026-10-13T10:00:00Z",
    "assets": [],
    "tarball_url": "https://diffinery.com/api/v3/repos/acme/website/tarball/v1.2.0",
    "zipball_url": "https://diffinery.com/api/v3/repos/acme/website/zipball/v1.2.0",
    "body": "## What's Changed\n* Fix broken footer links by @diffy in https://diffinery.com/acme/website/pull/41\n\n**Full Changelog**: https://diffinery.com/acme/website/compare/v1.1.0...v1.2.0",
    "reactions": { "url": "https://diffinery.com/api/v3/repos/acme/website/releases/7/reactions", "total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "confused": 0, "heart": 0, "hooray": 0, "rocket": 0, "eyes": 0 },
    "latest": true
  },
  "repository": { "id": 6628874657374920, "name": "website", "full_name": "acme/website", "private": false, "visibility": "public", "html_url": "https://diffinery.com/acme/website" },
  "sender": { "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90", "login": "diffy", "type": "User" }
}

Note: Webhooks receive events for drafts, which only people with the write role can see in the web UI and API. Treat release payloads from private repositories, and drafts in public ones, as private.

status

Sent every time a commit status is posted with POST /repos/{owner}/{repo}/statuses/{sha}. There is no action. See Checks and commit statuses.

FieldTypeDescription
idnumberThe status id
shastringThe commit
namestringThe repository's full name, owner/name
statestringpending, success, failure or error
contextstringThe status context (default when none was given)
descriptionstring or nullThe description
target_urlstring or nullThe link to the build
commit.shastringThe commit, again
created_at, updated_atstringWhen the status was posted (ISO 8601 with milliseconds)
{
  "id": 17,
  "sha": "9c4d8e2f1a0b3c5d7e9f2a4b6c8d0e1f3a5b7c9d",
  "name": "acme/website",
  "state": "success",
  "context": "ci/external",
  "description": "Build #812 passed in 3m 12s",
  "target_url": "https://ci.example.com/builds/812",
  "commit": { "sha": "9c4d8e2f1a0b3c5d7e9f2a4b6c8d0e1f3a5b7c9d" },
  "created_at": "2026-10-13T09:40:00.000Z",
  "updated_at": "2026-10-13T09:40:00.000Z",
  "repository": { "id": 6628874657374920, "name": "website", "full_name": "acme/website", "private": false, "visibility": "public", "html_url": "https://diffinery.com/acme/website" },
  "sender": { "id": "c1b2a3d4e5f60718293a4b5c6d7e8f9a", "login": "ci-bot", "type": "User" }
}

Unlike GitHub's status payload, commit holds only the SHA and there is no branches list. Check runs don't send webhook events.

deployment

Sent when a deployment is created:

  • with POST /repos/{owner}/{repo}/deployments (sender: who created it);
  • when a workflow job with environment: starts (sender: who started the run), as on GitHub. The deployment event comes first, then a deployment_status event with in_progress. Such a deployment has the task deploy, description null, payload {}, and ref set to the branch or tag name the run is for.
ActionSent when
createdA deployment is created
FieldTypeDescription
actionstringcreated
deployment.idnumberThe deployment id
deployment.shastringThe commit being deployed
deployment.refstringThe ref as it was given (branch, tag or SHA)
deployment.taskstringThe task, deploy by default
deployment.environmentstringThe environment, production by default. Environment names are case-insensitive: a deployment to Production when a production environment exists reports production.
deployment.descriptionstring or nullThe description
deployment.payloadanyThe payload sent when creating the deployment: the JSON value, or the string when it wasn't JSON. {} by default.
deployment.created_atstringCreation time (ISO 8601 with milliseconds)
{
  "action": "created",
  "deployment": {
    "id": 3,
    "sha": "9c4d8e2f1a0b3c5d7e9f2a4b6c8d0e1f3a5b7c9d",
    "ref": "main",
    "task": "deploy",
    "environment": "production",
    "description": "Deploy website 1.2.0",
    "payload": { "version": "1.2.0" },
    "created_at": "2026-10-13T10:05:00.000Z"
  },
  "repository": { "id": 6628874657374920, "name": "website", "full_name": "acme/website", "private": false, "visibility": "public", "html_url": "https://diffinery.com/acme/website" },
  "sender": { "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90", "login": "diffy", "type": "User" }
}

deployment_status

Sent when a deployment gets a new status:

  • with POST /repos/{owner}/{repo}/deployments/{id}/statuses;
  • when a workflow job with environment: starts (in_progress, sender: who started the run);
  • when that job finishes (success; inactive when it was cancelled or skipped; failure otherwise; sender: who started the run).

When a success status makes older deployments (created earlier) to the same environment inactive, those inactive statuses don't send events. Deployments created later than the one that succeeded are left as they are.

ActionSent when
createdA status is added
FieldTypeDescription
actionstringcreated
deployment_status.idnumberThe status id
deployment_status.statestringerror, failure, inactive, in_progress, queued, pending or success
deployment_status.descriptionstringThe description, up to 140 characters; empty when none
deployment_status.environmentstringThe environment
deployment_status.environment_urlstringThe deployed site's URL; empty when none
deployment_status.log_urlstringThe deployment's log (for jobs, the job's page); empty when none
deployment_status.created_atstringWhen the status was added (ISO 8601 with milliseconds)
deployment.id, .sha, .ref, .task, .environmentThe deployment the status belongs to
{
  "action": "created",
  "deployment_status": {
    "id": 5,
    "state": "success",
    "description": "",
    "environment": "production",
    "environment_url": "https://www.acme.example",
    "log_url": "https://diffinery.com/acme/website/actions/runs/8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a/job/2f1e0d9c8b7a69584736251403f2e1d0",
    "created_at": "2026-10-13T10:07:44.000Z"
  },
  "deployment": {
    "id": 3,
    "sha": "9c4d8e2f1a0b3c5d7e9f2a4b6c8d0e1f3a5b7c9d",
    "ref": "main",
    "task": "deploy",
    "environment": "production"
  },
  "repository": { "id": 6628874657374920, "name": "website", "full_name": "acme/website", "private": false, "visibility": "public", "html_url": "https://diffinery.com/acme/website" },
  "sender": { "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90", "login": "diffy", "type": "User" }
}

deployment_review

Sent when a workflow run starts waiting on an environment's required reviewers, and when a reviewer decides, on the run page or with POST /repos/{owner}/{repo}/actions/runs/{run_id}/pending_deployments. An environment with only a wait timer sends no event.

ActionSent whenSender
requestedA job that names the environment becomes ready and has to wait for a reviewer (once per environment and run)Who started the run
approvedA reviewer approves the environment for the runThe reviewer
rejectedA reviewer rejects itThe reviewer
FieldTypeDescription
actionstringrequested, approved or rejected
sincestringWhen the event happened (ISO 8601)
environmentstringThe environment's name
environment_idnumber or nullThe environment's id, as GET .../environments shows it
statestringapproved or rejected. Absent on requested.
commentstringThe reviewer's comment; empty on requested or when none was given
workflow_runobjectid, run_number, name, display_title, head_sha, head_branch and html_url of the run
reviewersarrayThe environment's required reviewers, each { "type": "User", "reviewer": { "login", "id" } }
requestorobject{ "login" } of who started the run. Only on requested.
approverobject{ "login" } of the reviewer who decided. Only on approved and rejected.
{
  "action": "approved",
  "since": "2026-10-13T10:12:30.000Z",
  "environment": "production",
  "environment_id": 4,
  "comment": "Release notes look good",
  "state": "approved",
  "workflow_run": {
    "id": "8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a",
    "run_number": 128,
    "name": "Deploy",
    "display_title": "Release 2.4",
    "head_sha": "4f1c2e9d0b7a6c5e3f2d1c0b9a8e7d6c5b4a3f2e",
    "head_branch": "main",
    "html_url": "https://diffinery.com/acme/website/actions/runs/8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a"
  },
  "reviewers": [{ "type": "User", "reviewer": { "login": "alice", "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90" } }],
  "approver": { "login": "alice" },
  "repository": { "id": 6628874657374920, "name": "website", "full_name": "acme/website", "private": false, "visibility": "public", "html_url": "https://diffinery.com/acme/website" },
  "sender": { "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90", "login": "alice", "type": "User" }
}

Unlike GitHub, workflow_run holds only the fields above and reviewers[].reviewer.id is the Diffinery account id.

repository_dispatch

Sent when someone with the write role calls POST /repos/{owner}/{repo}/dispatches. The same request starts workflows with on: repository_dispatch (see Events that trigger workflows).

curl -sS -X POST -H "Authorization: Bearer dfy_..." -H "Content-Type: application/json" \
  https://diffinery.com/api/v3/repos/acme/website/dispatches \
  -d '{ "event_type": "deploy-docs", "client_payload": { "version": "1.2.0" } }'
FieldTypeDescription
actionstringThe event_type from the request (1 to 100 characters)
branchstringThe repository's default branch
client_payloadobjectThe client_payload from the request ({} when none): at most 10 top-level keys and 64 KB
{
  "action": "deploy-docs",
  "branch": "main",
  "client_payload": { "version": "1.2.0" },
  "repository": { "id": 6628874657374920, "name": "website", "full_name": "acme/website", "private": false, "visibility": "public", "html_url": "https://diffinery.com/acme/website" },
  "sender": { "id": "f0e1d2c3b4a5968778695a4b3c2d1e0f", "login": "review-bot", "type": "Bot", "on_behalf_of": "diffy" }
}

workflow_dispatch

Sent when someone with the write role starts a workflow by hand: Run workflow in the web UI, diffinery workflow run, POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches, or the dispatch_workflow MCP tool. It is sent once the request is accepted (the workflow exists at the ref, has a workflow_dispatch trigger and the inputs are valid), alongside the workflow_run event with "action": "requested" for the new run. There is no action.

FieldTypeDescription
refstringThe full ref the workflow runs on, for example refs/heads/main or refs/tags/v1.2.0
workflowstringThe workflow file's path, for example .github/workflows/deploy.yml
inputsobjectThe inputs after defaults and type conversion: booleans and numbers keep their types (GitHub sends strings). Inputs the workflow doesn't declare are dropped.
atstringTime of the dispatch
{
  "ref": "refs/heads/main",
  "workflow": ".github/workflows/deploy.yml",
  "inputs": { "environment": "staging", "version": "1.2.0", "dry-run": false },
  "at": "2026-10-13T10:15:00.000Z",
  "repository": { "id": 6628874657374920, "name": "website", "full_name": "acme/website", "private": false, "visibility": "public", "html_url": "https://diffinery.com/acme/website" },
  "sender": { "id": "a1b2c3d4e5f60718293a4b5c6d7e8f90", "login": "diffy", "type": "User" }
}

The workflow's own inputs are in the workflow_dispatch trigger.

Events that are never delivered

Audit entries (sign-ins, push protection decisions, settings changes) are not sent to webhooks. They are in the audit log. Check runs and check suites (GitHub's check_run and check_suite) send no events either; commit statuses send status. Transferring or renaming a repository sends no event (GitHub sends a repository event); a webhook keeps receiving deliveries afterwards, with the new full_name and html_url.

An import from GitHub sends no events while it fills a repository: the branches and tags it pushes send no push, and the issues, pull requests, comments, reviews and releases it copies send no issues, pull_request, issue_comment, pull_request_review or release, so a webhook added during an import isn't flooded with old history and no workflows start. Changes that people, agents and workflows make send events as usual. A repository imported with a homepage on GitHub has it as homepage in the repository object; otherwise homepage is null.

Comparing with GitHub payloads

EventFields GitHub sends that Diffinery does not
pushcommits, head_commit, pusher, compare, base_ref
pull_requestThe full pull request object (title, body, user, labels, state, draft, timestamps), and changes for edited
pull_request_reviewreview and pull_request objects
issuesThe full issue object, and changes for edited
issue_commentcomment object, full issue object
workflow_runworkflow_run and workflow objects
releaseNothing in the release object, which has the REST API shape
statusbranches and the full commit object
deploymentMost deployment fields (url, creator, statuses_url, transient_environment, production_environment, ...)
deployment_statusMost deployment_status fields (url, creator, target_url, ...) and the full deployment object
repository_dispatchNothing: the same action, branch and client_payload
workflow_dispatchNothing: the same inputs, ref and workflow (inputs keep their types)

All events also lack GitHub's installation and organization objects. The repository object is complete apart from counters (always 0) and permissions. Use the REST API to fetch any detail you need, keyed by number, commentId or runId. See Webhooks for delivery behavior and signature verification.