Skip to content

Webhooks

Overview

Tilt sends a signed HTTP POST to your registered endpoint URL when a payment changes state. Use webhooks instead of polling for latency-sensitive or high-volume integrations.

Register endpoint URLs in the admin portal under Partner → Integrations → Webhooks.

Event types

Event kindFired when
payment.approvedProcessor approves the payment
payment.refundedA refund is confirmed by the processor
payment.voidedA payment is voided before settlement
payment.cancelledA pending payment is cancelled by an operator
payment.expiredA pending payment’s TTL elapses without resolution
payment.reversal_settledA reversal (refund/void on settled charge) is confirmed
payment.reversal_failedA reversal is rejected by the processor — manual intervention required

Event envelope

{
"v": 1,
"event_id": "evt_01j2…",
"event_kind": "payment.approved",
"occurred_at": "2024-01-15T10:30:47Z",
"partner_id": "uuid",
"resource": {
"payment_id": "uuid",
"order_id": "uuid",
"status": "approved",
"amount_cents": 5000,
"method": "card",
"external_reference": "INV-001"
}
}

event_id is a stable UUID — safe to use as an idempotency key.

Signature verification

Every webhook POST includes an X-Tilt-Signature header:

X-Tilt-Signature: hmac-sha256=3d5a0b…

The signature is HMAC-SHA256 over the raw request body using the signing secret configured on your endpoint.

import crypto from "node:crypto";
function verifySignature(rawBody, sigHeader, secret) {
const expected = "hmac-sha256=" + crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(sigHeader),
Buffer.from(expected)
);
}

Retry policy

If your endpoint returns a non-2xx response or times out (>30s), Tilt retries with exponential backoff:

AttemptDelay
1immediate
21 minute
35 minutes
430 minutes
52 hours
612 hours

After 6 failed attempts the delivery is moved to a dead-letter queue. You can replay DLQ deliveries from the admin portal → Integrations → Webhooks.

Idempotency

event_id is stable across retries. Your listener must deduplicate on event_id — the same event may be delivered more than once (at-least-once delivery guarantee).

Recommended pattern: record event_id in your own database on first receipt; discard if already seen.

Ordering

Events are delivered in rough chronological order but not guaranteed to be strictly ordered. For example, payment.approved could arrive before a payment.failed for a different payment on the same order. Always treat each event independently and re-read the order state from the API if you need the full picture.

Registering an endpoint

Register endpoints in the admin portal under Partner → Integrations → Webhooks, or via the API:

POST /admin/v1/partners/{partner_id}/webhook-endpoints

Required scope: tilt/webhooks:subscribe (granted by super_admin).

Each endpoint has:

  • A URL (must be https://)
  • An event-kind filter (subscribe to all events, or a specific subset)
  • A Tilt-generated signing secret (shown once at creation — copy it before dismissing)