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:readorcontents: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.
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.
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.updatedwithchanges.head_shaand alsoref.updated. Key builds and check runs on the commit SHA, and start at most one build per SHA. 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. - Verify the signature with the key the header’s
kidnames, from the delivery JWKS URL Entire gave you at registration, todayhttps://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 unknownkid. Don’t fetch keys from a URL built fromiss. - Check that
issishttps://entire.ioor anhttpsorigin on a host underentire.io, such ashttps://us-east-2.entire-ci-webhooks.entire.io. - 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.
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.
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:
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.