Skip to main content
Entire sends installation, ref and trail events to the webhook URL you register. Use installation events to keep track of your access, ref events to react to pushes, and trail events for work such as deploy previews. They use the same delivery format and signature checks.
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:read or contents:write on 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 under event.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.created and installation.updated: an installation was created or its settings changed.
  • installation.repositories_added and installation.repositories_removed: repositories joined or left the installation.
  • installation.suspended: access stopped.
  • installation.resumed: access resumed.
  • installation.deleted: the installation was removed.
Installation fields are under 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.
Existing trails are not backfilled with 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.updated with changes.head_sha and also ref.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_sha can be null. trail.opened can arrive before Entire records a commit. Read the branch yourself, or wait for the trail.updated that sets head_sha.
  • Order isn’t guaranteed. A retry can arrive after a later event for the same trail. trail is the state at trail.updated_at: ignore an event older than the state you hold.
  • actor and trail.author can 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 carries Authorization: Bearer <token>, an EdDSA JWT:
  1. Keep the exact bytes of the request body.
  2. Check that the token header’s alg is EdDSA.
  3. Check iss against the delivery issuers you trust. Configured production issuers are listed below. Don’t accept a host just because it ends in entire.io.
  4. Verify the signature with the key the header’s kid names. 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 unknown kid. Never fetch keys from an unchecked iss, and don’t use a Core’s receipt-signing keys for deliveries.
  5. Check that aud is your plugin slug.
  6. Check iat and exp. A token is valid for 5 minutes.
  7. Compare the body_sha256 claim with the unpadded base64url SHA-256 of the raw body. Do this before you parse the JSON.
  8. Reject a jti you’ve already seen. Each attempt, including a retry, carries a new token and jti.
Configured production delivery issuers are:
These are different from the plugin JWT issuer and receipt audience. A delivery’s audience is still your slug, not 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 same delivery_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.