Skip to main content

Webhooks

Webhooks notify your backend when payment, withdrawal, and payout-routing state changes. Use webhooks for real-time updates, then fetch the canonical PayChain resource before releasing value.
Source of truth: A webhook tells you an event happened. The invoice, withdrawal, or routing record remains the canonical state for fulfillment and reconciliation.

Delivery contract

PayChain webhook delivery is at least once. Events can be retried, manually replayed, duplicated, delayed, or delivered after the underlying invoice, withdrawal, or payout-routing resource has already moved to a later state. Do not assume strict ordering across event families, such as invoice.* before withdrawal.*, or across retries for the same resource. Use these identifiers intentionally:
  • Top-level id and X-Webhook-ID identify this webhook delivery event. Store this value to ignore duplicate deliveries of the same event.
  • data.resourceId identifies the changed PayChain resource. It matches data.invoiceId, data.withdrawalId, or data.payoutRoutingId depending on data.resourceType.
  • data.txHash identifies the on-chain transaction when one exists.
  • data.clientReference is your own reference when supplied on the original API request.
Treat lifecycle events as state synchronization, not commands. For example, withdrawal.completed means a withdrawal resource reached completed; it must not create or submit another withdrawal in your system.

What webhooks are for

Use webhooks to:
  • Mark an order as paid.
  • Release a digital good or service.
  • Update a customer deposit ledger.
  • Track withdrawal progress.
  • Track automated payout routing progress.
  • Alert your team when payout routing fails or a withdrawal is blocked.

Signature verification

Verify every webhook using the raw request body.
Raw body note: Do not parse JSON and then re-stringify it before verification. Signature verification must use the exact raw bytes PayChain sent.

Express raw-body example

Next.js route handler example

  1. Receive the webhook request.
  2. Read the raw body.
  3. Verify the signature and timestamp.
  4. Store the top-level webhook id or X-Webhook-ID and return 2xx for duplicates.
  5. Use data.resourceId and data.resourceType to fetch the canonical invoice, withdrawal, or routing record from PayChain.
  6. Apply resource state transitions idempotently. Do not move a completed local payout back to processing because an older event arrived late.
  7. Treat withdrawal.* events as status updates only. Never create a new withdrawal from a withdrawal.completed webhook.
  8. Return a 2xx response quickly.

Common event families

Common examples:
  • invoice.paid
  • invoice.overpaid
  • invoice.failed
  • withdrawal.created
  • withdrawal.processing
  • withdrawal.completed
  • withdrawal.failed
  • invoice.payout_routing.started
  • invoice.payout_routing.completed
  • invoice.payout_routing.failed

Example payloads

Webhook payloads include a webhook delivery ID, event type, business ID, timestamp, and a data object for the resource that changed. The top-level id is the webhook delivery/event ID. The changed resource ID is inside data.id and also in the typed field, such as data.invoiceId or data.withdrawalId. Use event as the canonical event type; type is included as a compatibility alias.

Invoice paid

Withdrawal completed

Payout routing failed

Fulfillment note: Do not fulfill from the webhook payload alone. Verify the signature, store the event ID, fetch the invoice or withdrawal from PayChain, then apply your business action once.

Retry behavior

If your endpoint does not return a successful response, PayChain retries delivery according to the configured retry policy. Repeated failures eventually move the delivery to a failed state. Retryable failures, manual retries, and manual replays can cause older events to arrive after newer resource states. Return a 2xx response only after your system has accepted the event for processing. If you need more time, store the event and process it asynchronously; do not hold the webhook request open for long-running fulfillment, payout, or customer-notification work.
Manual recovery: Failed webhook events can be retried or replayed from the webhook event APIs where supported. A replay creates a fresh delivery event.

API retry guidance

Use an Idempotency-Key for every mutating API call, including createWithdrawal. If a createWithdrawal request times out or returns a 5xx, retry with the same Idempotency-Key or fetch the existing withdrawal by the returned withdrawalId, your clientReference, or the txHash once available. Do not submit a fresh withdrawal request with a new idempotency key until you have confirmed the original request did not create a withdrawal.

Endpoint management

Use the webhook API to:
  • Get webhook configuration.
  • Update the webhook URL or enabled state.
  • Rotate the webhook signing secret.
  • Send a test webhook.
  • List webhook events.
  • Retry or replay failed events.
See OpenAPI reference for the exact endpoint schemas.

Common mistakes

  • Treating webhook delivery as fulfillment without fetching the canonical resource.
  • Not storing processed webhook IDs.
  • Treating withdrawal.completed as a command to create another payout.
  • Assuming webhook event order matches invoice, sweep, or withdrawal execution order.
  • Retrying a timed-out createWithdrawal request with a new idempotency key before checking the canonical withdrawal.
  • Verifying against parsed JSON instead of raw body.
  • Taking too long before responding.
  • Using the same webhook URL and secret for sandbox and live.