Home
Advocacy Webhooks

Receiving Webhooks

How we deliver events to your endpoint, retry failures, and what we expect back.

When something happens in your team, we record an event for every active webhook subscribed to it and send it to the webhook's url shortly after, in the background.

The request

By default, events are sent with POST and a JSON:API document as the body, in the same format as our other API responses: a resource of type event whose object relationship points to the resource the event is about (sideloaded in included) and whose team relationship points to your team. The Webhook Events pages describe each event and its payload. Fields that the REST API only returns for particular OAuth scopes are omitted from payloads.

{
  "data": {
    "id": "1377451",
    "type": "event",
    "attributes": {
      "name": "asset_created",
      "created_at": "2025-03-14T16:42:07.000Z"
    },
    "relationships": {
      "object": { "data": { "type": "asset", "id": "5501" } },
      "team": { "data": { "type": "team", "id": "1" } }
    }
  },
  "included": [
    { "type": "asset", "id": "5501", "attributes": { "name": "Trail boots hero shot" } }
  ]
}

Every request carries these headers:

  • Content-Type: application/vnd.api+json
  • User-Agent: TINT Webhook/2.0
  • API-Version: the API version the payload was serialized with.
  • X-TINT-Signature: only when the webhook has a signing secret. See Webhook Event Signatures.
  • The webhook's own headers, which can override the first three.

A webhook can change all of this: filter limits which events are sent, payload_include and payload_fields change what the document contains, http_method changes the verb, and payload_transformation_template replaces the body with whatever the template renders. The payload is rendered once, when the event is recorded, so every retry sends the same body, and changing a webhook doesn't affect events already recorded.

Responding

Respond quickly with a 2XX status to acknowledge the event, and do any slow work afterwards. Any status below 400 counts as delivered; redirects are not followed. Your response body is ignored, but stored with the attempt in the event log.

  • 404 Not Found or 410 Gone: we stop delivering that event immediately and mark it failure.
  • Any other 4XX or 5XX status, a timeout or a connection error: we retry the event up to 7 times with an increasing delay (roughly 1 minute, 1 minute, 2 minutes, 13 minutes, 1 hour, 4 hours and 13 hours after each failure, plus some random jitter), so about 18 hours in total, then mark it failure.
  • Too many failures: once a webhook accumulates more than 50 failed attempts within a week without a successful delivery in between, we set it to inactive and stop recording events for it. Fix your endpoint, then update the webhook with status: active. Events that occurred while it was inactive are not sent.

Before every attempt, we also check that the webhook is still active and that its URL still uses https and resolves to a public IP address; if not, the event is marked failure without being sent.

Duplicates and ordering

  • Use the event id (data.id) to deduplicate. It stays the same across retries, and the same event can occasionally reach you more than once (for example, when your endpoint processed it but responded too late).
  • Don't rely on the order of events. Events are delivered concurrently and retried independently, so an asset_updated can arrive before the matching asset_created, and a retried event can arrive after newer ones. Compare created_at, or retrieve the current state of the resource from the API, when order matters.
  • The signature timestamp changes on every attempt (each attempt is signed again), while the body and data.attributes.created_at don't.
  • Events that were never delivered can be listed with the event log and filter[status]=failure.