Skip to main content
Entire sends your webhook URL an event when a trail opens, changes, closes, reopens or merges on a repository your plugin is installed on. A trail can open on a branch that already exists, without a push, so these events are the only way to see every trail. Build per-trail work, such as deploy previews, on them, and comment on the trail to report the results.
Trail events 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.
  • contents:read or contents:write. A trail event names a branch and a title, so Entire sends trail events only to plugins that can read contents. Without either permission you still receive ref and installation events, but no trail events, and nothing tells you so.
  • An active installation. You receive events for the repositories an installation covers, while it’s active. They stop when the installation is suspended or deleted.
Trail events arrive on the same URL as ref and installation events.

Events

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.
  • Any change to a trail that is already closed or merged.
A trail opened before trail events launched on 19 October 2026 never sends trail.opened. Its first event is its next change.

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. Key builds and check runs on the commit SHA, and start at most one build per SHA.
  • 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. Verify the signature with the key the header’s kid names, from the delivery JWKS URL Entire gave you at registration, today https://us-east-2.entire-ci-webhooks.entire.io/.well-known/jwks.json. Every region signs with the same key set. Cache it, and fetch it again when you see an unknown kid. Don’t fetch keys from a URL built from iss.
  4. Check that iss is https://entire.io or an https origin on a host under entire.io, such as https://us-east-2.entire-ci-webhooks.entire.io.
  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.
Answer a request that fails verification with 401: Entire doesn’t retry a 4xx. 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.

With the Go SDK

App.WebhookHandler verifies each delivery, skips deliveries it has already handled, and answers as described above. Decode a trail event with Event.TrailChange:
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, which protects a single process. If you run more than one instance, set Config.DeliveryStore and Config.ReplayStore to stores shared across instances, such as your database.