Webhooks are experimental. Field names and event types may change before the contract is final.
What you need
- A webhook URL. Entire registers it with your plugin, see Register your plugin. It must answer directly: Entire doesn’t follow redirects.
- An active installation. Ref and trail events cover the repositories the customer selected. They stop when the installation is suspended or deleted. Installation events tell you about those changes.
- Live
contents:readorcontents:writeon each covered repository for ref and trail events. Register the permission and have the customer accept it. Installation lifecycle events do not require repository contents permission.
Delivery format
Every request identifies the installation at the top level. The event’s body is underevent.payload:
installation_id is your installation key. There is no event.payload.installation.id. The project object and management link may also be present in the installation payload.
core_url can differ from the issuer of the install receipt. Check it against the Cores you trust, but don’t include either host in your installation key. For repository API calls, refresh the repository’s placement through the repository list; hosts can change.
Events
Installation events
installation.createdandinstallation.updated: an installation was created or its settings changed.installation.repositories_addedandinstallation.repositories_removed: repositories joined or left the installation.installation.suspended: access stopped.installation.resumed: access resumed.installation.deleted: the installation was removed.
event.payload.installation. Keep the highest revision you have applied and ignore an event at or below it. If revisions skip, read the repository list again rather than applying a repository delta to an incomplete list.
On created and updated events, replace your stored selection with the event’s selection. repository_selection is all or selected. When no repositories match, repository_count and repository_ids are absent: treat them as zero and an empty list. When repositories_omitted is true, the list was too long to include; fetch the repository list instead of clearing your stored selection.
For repository additions and removals, apply added_repository_ids or removed_repository_ids. Stop work on removed repositories.
On suspension, stop work and discard cached access tokens. suspended_by is plugin or customer; only that side can resume the installation. On resumption, refresh the repository list and mint new tokens. On deletion, discard credentials and remove the installation from your active records.
Don’t wait for a webhook to handle revoked access. If an API call says the installation or repository is no longer available, stop acting on it.
Ref events
Ref events describe changes to branches and tags. Use them to start work when a customer pushes to a repository.
Every ref event carries
repository, ref, before, after and forced. Use repository.id as the repository key and the returned repository.entire_clone_url for checkout.
ref includes refs/heads/ for a branch or refs/tags/ for a tag. Don’t try to read or check out a zero object ID. If you only handle branches, ignore tag events and answer 2xx.
For work triggered by a push, use the event’s after SHA rather than the branch’s current head, which may have changed by the time you start. Your service decides whether a deletion or force push cancels earlier work; report any cancellation through the checks API.
Trail events
A trail can open on a branch that already exists, without a push. Use trail events when you need to see that, and comment on the trail to report results.
Every event except
trail.opened carries changes: an object keyed by state, title, branch, base or head_sha, each with from and to. trail.closed, trail.reopened and trail.merged always carry changes.state.
Entire sends nothing for:
- A deleted trail.
- Comments, discussions, reviews or approvals.
- A description edit.
- Ordinary edits to a closed or merged trail. Reopening is an explicit exception.
trail.opened when delivery is enabled. Their next qualifying change can produce an event.
Handle duplicates and order
- Deduplicate on
delivery_id. It stays the same across retries, so the same delivery can arrive more than once. Answer a repeat with 2xx. - Expect a commit twice. A push to a trail’s branch sends
trail.updatedwithchanges.head_shaand alsoref.updated. If both trigger the same work, deduplicate it by installation, repository, commit and the kind of work you run. Allow an explicit rerun to start a new attempt. head_shacan be null.trail.openedcan arrive before Entire records a commit. Read the branch yourself, or wait for thetrail.updatedthat setshead_sha.- Order isn’t guaranteed. A retry can arrive after a later event for the same trail.
trailis the state attrail.updated_at: ignore an event older than the state you hold. actorandtrail.authorcan be null when Entire doesn’t know who acted, such as for a push or a change on the forge.- Answer 2xx to event types you don’t handle. Entire may add event types to the same URL.
Verify a delivery
The Go SDK does this for you. Without it, verify each request before you trust its body. Each delivery carriesAuthorization: Bearer <token>, an EdDSA JWT:
- Keep the exact bytes of the request body.
- Check that the token header’s
algisEdDSA. - Check
issagainst the delivery issuers you trust. Configured production issuers are listed below. Don’t accept a host just because it ends inentire.io. - Verify the signature with the key the header’s
kidnames. Use the delivery JWKS URL Entire gave you at registration, or the trusted issuer’s/.well-known/jwks.json. Cache the keys, and fetch them again for an unknownkid. Never fetch keys from an uncheckediss, and don’t use a Core’s receipt-signing keys for deliveries. - Check that
audis your plugin slug. - Check
iatandexp. A token is valid for 5 minutes. - Compare the
body_sha256claim with the unpadded base64url SHA-256 of the raw body. Do this before you parse the JSON. - Reject a
jtiyou’ve already seen. Each attempt, including a retry, carries a new token andjti.
urn:entire:plugin:{slug}.
Answer a request that fails verification with 401, which Entire doesn’t retry. If you can’t fetch the key set, answer 503 so that Entire retries later.
Respond
Answer 2xx within 10 seconds, as soon as the delivery is verified and stored, and do slow work from a queue. Entire retries a 429, a 5xx, a timeout or a network error up to 7 times over about 15 minutes, with the samedelivery_id. Any other status, including a redirect, fails the delivery for good.
You can return Retry-After in seconds. Entire caps it at its longest retry interval and ignores the HTTP-date form.
With the Go SDK
App.WebhookHandler verifies each delivery and checks its delivery store for handled IDs. It returns 401 for authentication failures, 400 for malformed bodies or plugin mismatches, 405 for a wrong method, 413 for an oversized body, 503 when keys are unavailable, and 500 when your callback fails. It always fetches keys from Config.DeliveryJWKSURL, not from the token’s issuer. Its default issuer policy is looser than the list above: it accepts any HTTPS origin under entire.io or entire.com, plus the legacy issuers https://entire.io and https://entire.com. Set Config.DeliveryIssuer to pin one issuer.
Decode a trail event with Event.TrailChange and pass it to your own storage function:
saveAndQueue is application code, not an SDK method. In one database transaction, lock the stored trail, compare change.Trail.UpdatedAt with the stored timestamp, and ignore older snapshots. Store an accepted snapshot and queue work before committing. Key the record by installation, repository and trail ID. Return storage failures so Entire retries the delivery.
Workers must also serialize work for each trail and check its latest stored state before acting. For a preview service, a closed or merged trail means teardown; an open trail with a head SHA can create or update a preview. This prevents a delayed update or queued job from recreating a preview after closure. Delivery deduplication alone doesn’t enforce that order.
The SDK turns a null branch, head_sha, from or to into an empty string.
The SDK’s default stores keep handled deliveries and seen tokens in memory. Use durable shared Config.DeliveryStore and Config.ReplayStore implementations across instances and restarts. The handled check, callback and handled marker are separate operations; a marker-write failure is logged but still answers 200. Use delivery_id as a unique queue key to prevent duplicate jobs, and make worker actions idempotent before retrying them.