Skip to content

Retry & Delivery Policy

Delivery flow

  1. When an event occurs, TokenCashFlow creates a webhook_delivery_job record and queues it
  2. The webhook worker picks up the job and sends an HTTP POST to your configured URL
  3. Timeout: 10 seconds per attempt
  4. Success: HTTP 2xx response → job marked success; no further attempts
  5. Failure: Non-2xx response, connection error, or timeout → job marked failed; retry scheduled

Retry schedule

Retries use an exponential backoff schedule. The default schedule (configurable by the platform operator):

AttemptDelay after previous failure
1 (initial)Immediate
230 seconds
360 seconds
45 minutes
510 minutes
630 minutes
71 hour
82 hours
94 hours
108 hours

After all 10 attempts are exhausted the job transitions to permanently_failed. No further delivery attempts are made automatically.

Total retry window: approximately 16 hours.

After permanent failure

Once a job reaches permanently_failed:

  • The delivery log is retained and viewable in Dashboard → Webhooks → Delivery History
  • The admin team receives an alert (configurable)
  • You can still retrieve payment status via GET /v1/payments/{payment_id} to sync your system

If your endpoint was down for an extended period, query the API to reconcile any events you may have missed.

What counts as success

Any HTTP response in the 2xx range (200–299) is treated as success. The body content is ignored.

Your endpoint should return a 2xx status quickly — do not perform long-running operations synchronously in the webhook handler. Acknowledge the webhook immediately, then process asynchronously.

Idempotency

Webhooks are delivered at-least-once. Duplicate deliveries are rare but possible (e.g. your endpoint returned 2xx but the network timed out before TokenCashFlow received the response).

Design your handler to be idempotent:

python
def handle_payment_completed(payment_id: str, net_amount_usd: float):
    # Check if already processed
    if Order.objects.filter(payment_id=payment_id, status="fulfilled").exists():
        return  # Already handled — safe to ignore

    # Process for the first time
    order = Order.objects.get(payment_id=payment_id)
    order.status = "fulfilled"
    order.net_amount = net_amount_usd
    order.save()
    send_fulfillment_email(order)

Use data.payment_id + event as a natural deduplication key.

Monitoring delivery health

  • Dashboard: Client Portal → Webhooks → Delivery History shows per-event status and attempt counts
  • API: GET /v1/webhooks/deliveries?status=permanently_failed to programmatically check for failures
  • Alert threshold: If too many deliveries permanently fail, contact support to investigate your endpoint health

TokenCashFlow Documentation