payable

Webhooks and Reconciliation

Receive signed provider events, deduplicate delivery, and reconcile the local billing ledger.

Prerequisites

  • A webhook-capable provider with its signing secret configured
  • A storage driver for webhook events and reconciled resources
  • Access to the raw request body and provider signature headers

Configuration

Register one route per provider when more than one provider is active. Preserve the raw payload; parsing and serializing it again can invalidate the signature.

const provider = request.params.provider;
const rawBody = request.rawBody;

Run the example

const result = await payable.receiveWebhook({
  provider,
  payload: rawBody,
  signature: String(request.headers['stripe-signature'] ?? ''),
  headers: Object.fromEntries(
    Object.entries(request.headers).map(([name, value]) => [name, String(value ?? '')]),
  ),
});

return {
  received: true,
  eventId: result.webhookEventId,
  duplicate: result.duplicate,
};

Return a successful HTTP response after Payable accepts the event. With an asynchronous queue, the worker performs reconciliation after receipt; with the default sync queue, processing completes in the request.

Expected result

Payable verifies the signature, stores the event, deduplicates it by tenant, provider, and provider event ID, then reconciles matching payments or subscriptions. Audit and outbox records are written with the same correlation context when configured.

Failure behavior

Invalid signatures throw InvalidWebhookSignatureError. Webhook receipt without storage fails with WEBHOOK_STORAGE_REQUIRED. With multiple providers, omitting provider is ambiguous and fails with WEBHOOK_PROVIDER_AMBIGUOUS. Do not acknowledge invalid events as accepted.