Webhook delivery

Every webhook Flint sends follows one contract: the same headers, the same envelope, the same retry schedule. To build and register a handler, follow the Webhooks guide. For the list of event types and their data payloads, see the event catalog.

Delivery contract#

PropertyBehavior
ProtocolHTTPS POST with a JSON body
User agentFlint-Webhooks/1.0
Timeout10 seconds per attempt
RedirectsNever followed
SuccessAny 2xx response from your server
FailureAnything else: a non-2xx status, a redirect, a timeout, or a connection error
GuaranteesAt-least-once delivery, in no guaranteed order
Webhook delivery
startfinal
Webhook deliverysigned HTTP POST2xx within 10 secondsanything elsebackoff, up to 8 more attempts over 72 hoursevent createddelivery attempteddeliveredwaiting to retry
  • event created moves to delivery attempted on signed HTTP POST
  • delivery attempted moves to delivered on 2xx within 10 seconds
  • delivery attempted moves to waiting to retry on anything else
  • waiting to retry moves to delivery attempted on backoff, up to 8 more attempts over 72 hours

Webhook URLs must be HTTPS and must resolve to a publicly routable address. Flint rejects localhost, private IPs, and internal hostnames both when you register the URL and again at delivery time.

Delivery headers#

HTTP
POST /webhooks/flint HTTP/1.1
Content-Type: application/json
User-Agent: Flint-Webhooks/1.0
X-Flint-Event-Type: payment_intent.succeeded
X-Flint-Webhook-ID: whev_1kmn0aExample
X-Flint-Webhook-Endpoint-ID: whep_1kmn0aExample
X-Flint-Signature: t=1783090410,v1=d2eb22206ed2470e912f9e4465586273c16748df664b5ee4ba2700a4694d92a9
webhook-id: whev_1kmn0aExample
webhook-timestamp: 1783090410
webhook-signature: v1,PLz/mlH/ybZHvKsfeKbKu8qA7eC6OjCoXuXAWqb4WFM=
HeaderPurpose
webhook-id, webhook-timestamp, webhook-signatureThe preferred identity and signature headers for new integrations, in Standard Webhooks format. See Verify with a standard webhooks library.
X-Flint-SignatureLegacy proof the delivery came from Flint. Verified in Step 4 of the Webhooks guide.
X-Flint-Webhook-IDLegacy event ID (whev_...). Identical on every retry and resend of the same event, which makes it your deduplication key.
X-Flint-Event-TypeThe event type, so you can route the delivery without parsing the body.
X-Flint-Webhook-Endpoint-IDThe endpoint (whep_...) this delivery was sent to.

The user agent is always Flint-Webhooks/1.0, which is useful when your infrastructure filters inbound traffic. Pick one signature scheme and verify it completely. There is no security benefit to checking both; they authenticate the same delivery with the same secret.

Event body#

JSON
{
  "webhook_event_id": "whev_1kmn0aExample",
  "event_type": "payment_intent.succeeded",
  "payload_version": 1,
  "mode": "test",
  "merchant_id": "mer_1kmn0aExample",
  "created_at": "2026-07-03T14:53:30Z",
  "request": {
    "id": "2f7c5a9d-6e4b-4f8a-9b73-1d8e4c6a0f25",
    "idempotency_key": "confirm-payment-1kmn0a-001"
  },
  "data": {
    "payment_intent": {
      "payment_intent_id": "pi_1kmn0aExample",
      "status": "succeeded",
      "amount_money": {"amount": 2500, "currency": "USD"},
      "order_id": "ord_1kmn0aExample"
    }
  }
}
FieldDescription
webhook_event_idUnique event ID. Identical on every retry and resend of this event; also sent as webhook-id and legacy X-Flint-Webhook-ID.
event_typeWhat happened. Every type is listed in the event catalog.
payload_versionEnvelope format version, currently 1.
modetest or live. An endpoint only receives events for the mode of the API key that created it.
merchant_idThe merchant the event belongs to.
created_atWhen the event was created (RFC 3339, UTC).
requestAlways present. null when the event has no originating API request. Otherwise an object containing both the request id and idempotency_key.
testPresent, and true, only on synthetic events from the test-events endpoint (Step 5 of the Webhooks guide). Real events in your sandbox carry "mode": "test" instead.
dataThe event payload. Its shape depends on the event type; the example above is payment_intent.succeeded.

Two forward-compatibility rules keep integrations from breaking as the platform grows: new event types appear over time (endpoints subscribed to everything receive them automatically), and existing payloads gain new fields. Ignore fields you don't recognize, ignore event_type values you don't handle, and return 2xx for those events so Flint does not retry them.

Retry schedule#

A delivery attempt fails on a non-2xx status, a redirect, a connection error, or a 10-second timeout. Failed deliveries retry on a fixed backoff schedule, up to 9 total attempts spanning about three days:

AttemptDelay after previous failureApproximate elapsed time
1none (immediate)0
230 seconds30 seconds
32 minutes2.5 minutes
410 minutes13 minutes
51 hour1.2 hours
64 hours5 hours
712 hours17 hours
824 hours41 hours
930 hours71 hours

Each delay gets up to 20 percent of random jitter added, so treat the elapsed times as approximate. Every attempt is signed fresh with a new timestamp, so retries always pass your tolerance check.

While retries remain, the event's status is pending with a next_retry_at. After the ninth failure it becomes failed and automatic delivery stops, but the event is never silently lost: it stays queryable, and you can resend it manually after fixing your handler.

Delivery order#

Events fan out independently, and a failed delivery retries while newer events keep flowing. A sequence like this is normal:

text
happened on Flint              arrived at your endpoint
------------------             -------------------------
1  subscription.created        1  subscription.payment_succeeded
2  subscription.payment_       2  subscription.activated
   succeeded                   3  subscription.created
3  subscription.activated         (first delivery failed; this is its retry)

Treat a webhook as a signal that something changed, not as the change itself. When your logic needs current state, fetch the resource: a GET /v1/orders/{order_id} reflects everything that has happened to that order regardless of what has arrived at your endpoint. The envelope's created_at lets you sequence events locally if you must, but fetching fresh state is simpler and harder to get wrong.

Lifecycle payloads include the resource ID, current public status, and resource_updated_at or an equivalent freshness timestamp. Some subresource events also include event-specific timestamps such as payment_attempt_updated_at; for invoice payment-attempt terminal events, resource_updated_at matches the payment-attempt transition time. If you mirror state locally, compare those timestamps before overwriting a newer row, and fetch the resource before irreversible work when stale data would be costly.

  • Webhooks guide: register an endpoint, verify signatures, and build a handler that survives retries.
  • Webhook events: every event type Flint delivers, with payload schemas.
  • Webhooks API: endpoints for managing endpoints, listing deliveries, and resending.
Rate this doc