Webhooks

Retries & delivery

Three attempts, exponential backoff, eight-second timeout. Build idempotent handlers and use the event ID for deduplication.

Schedule

Three attempts per event, with backoff:

AttemptDelay
1Immediate
2+1 second
3+4 seconds

If all three fail, the delivery is marked failed in the Hub and not retried automatically. You can manually retry any failed delivery from the Events tab.

Timeout

Each attempt has an 8-second request timeout. If your handler takes longer, return 2xx quickly and process asynchronously.

What's retryable

Retried:

  • Any 5xx response.
  • 408 Request Timeout.
  • 429 Too Many Requests.
  • Network errors and timeouts.

Not retried:

  • 2xx (success).
  • 3xx (redirect — return 2xx instead).
  • 4xx other than 408/429.

Idempotency

Every event has a stable id field (e.g. evt_a1b2c3d4e5f6). The same event ID persists across retries. Use it to deduplicate:

js
async function handle(event) {
  const seen = await redis.set(`pg:evt:${event.id}`, "1", "NX", "EX", 86400 * 7);
  if (seen !== "OK") return; // already processed
  await processCompletion(event);
}

A 7-day TTL is more than enough — Playgent retries within seconds, not days.

Ordering

Events are not strictly ordered. Two completions from the same user may arrive out of order, especially around retries. Don't rely on receive order — use completed_at from the payload for time-based logic.

Backpressure

If your handler is slow, Playgent's 8-second timeout will trigger retries. To avoid that:

  1. Receive the request.
  2. Verify the signature.
  3. Enqueue the payload to your own queue (SQS, Cloud Tasks, Inngest, etc.).
  4. Return 200.

Your worker processes the queue at its own pace.