Give Pay Documentation
Webhooks

Delivery & Retries

How GivePay delivers webhooks and what to expect on failure

Delivery pipeline

When a payment, refund, or subscription event occurs:

  1. GivePay creates a webhook event record (with a stable id and an idempotency key).
  2. It finds every active endpoint in the matching environment whose enabled_event list includes the event type (or "*").
  3. A delivery attempt is queued for each matching endpoint.
  4. Workers pull from the queue, sign the payload with that endpoint's secret, and POST it to your URL.
  5. Failed attempts are re-queued with exponential backoff until a 2xx is received or the attempt cap is hit.

Retry policy

SettingValue
Max attempts8 per delivery (initial attempt + 7 retries)
Request timeout~30 seconds per attempt
SuccessHTTP 2xx (200–299)
Retry triggersHTTP 4xx, 5xx, network errors, TLS errors, timeout

Retry schedule

When an attempt fails, the next retry is queued with a fixed delay. The delays escalate as the delivery accumulates failures:

AttemptDelay since previous failureCumulative time since first failure
1 (initial)0
21 minute1 min
35 minutes6 min
430 minutes36 min
52 hours≈ 2 h 36 m
68 hours≈ 10 h 36 m
724 hours≈ 1 d 10 h
848 hours≈ 3 d 10 h

After the 8th attempt fails, the delivery is marked EXHAUSTED and no further attempts are made. Your receiver therefore has ~3.4 days from the first failure to come back online before a delivery is permanently dropped from the retry queue. The dashboard surfaces exhausted deliveries so you can replay them once the issue is fixed.

Status values

StatusMeaning
PENDINGQueued but not yet attempted, or scheduled for retry
SUCCEEDEDReceiver returned a 2xx
FAILEDLast attempt failed; will retry until EXHAUSTED
EXHAUSTEDAll retry attempts used up

Best practices

Always acknowledge fast

Respond with a 2xx as soon as you've durably captured the event (e.g. written it to a queue or database). Don't do slow work inline:

  • Sending email or SMS
  • Hitting third-party APIs
  • Generating PDFs

A receiver that takes longer than ~30 seconds will be retried, leading to duplicates.

Deduplicate using event id

Webhooks are at-least-once. Persist a record of every event.id you've processed and skip duplicates:

async function handle(event) {
  const inserted = await db.processedEvents.insertIfMissing(event.id);
  if (!inserted) return; // already processed
  await enqueueWork(event);
}

Verify before parsing

Run signature verification on the raw bytes of the request body, before any JSON parsing. See Signatures.

Pin to event types you care about

Subscribing with ["*"] is convenient but means you'll receive event types added in future releases. Pin to specific types in production so a new event type doesn't surprise your code.

Use SANDBOX for testing

Create a separate environment: "SANDBOX" endpoint in the dashboard and exercise it with the dashboard's "Send test event" action and real sandbox checkouts. Sandbox events never touch a production receiver.

Monitor EXHAUSTED deliveries

The dashboard surfaces exhausted deliveries — hook that into your on-call alerting. An exhausted delivery means your receiver was unavailable for the full retry window.

Observability

For every delivery attempt, GivePay records and surfaces in the dashboard:

  • The full HTTP request (method, URL, headers, body)
  • The receiver's HTTP status code
  • The receiver's response body, truncated to 1 KB
  • Any transport-level error message
  • attempt_count, next_retry_at, last_attempt_at, completed_at

Source of truth

Webhooks are a notification mechanism, not the system of record. If your receiver has been down for an extended period or you miss events, reconcile with the REST API:

These return the same canonical objects emitted in webhook payloads.