Skip to main content

Webhooks

Webhooks are how ZaroPay tells your backend a payment happened. They are the authoritative signal for fulfilment — always prefer them over the customer's browser redirect.

Events

EventSubscribableDelivered today
deposit.confirmed✅ (default)✅ — the main payment event
deposit.latereserved (same payload shape, not yet emitted)
deposit.detectednot emitted yet
withdrawal.confirmednot emitted yet
withdrawal.failednot emitted yet

Today, build against deposit.confirmed. If you subscribe with no eventTypes, you get deposit.confirmed by default.

Payload — deposit.confirmed

This is the exact JSON body POSTed to your URL:

{
"id": "5f1c9d2e-7a4b-4c0a-9e21-3b8f0a1d2c34",
"event": "deposit.confirmed",
"createdAt": "2026-06-27T10:15:42.123Z",
"data": {
"depositId": "a1b2c3d4-…",
"merchantId": "m1m2m3m4-…",
"chain": "tron",
"currency": "USDT",
"amount": "100",
"grossAmount": "100",
"orderAmount": "100",
"fiatCurrency": null,
"fiatAmount": null,
"quoteRate": null,
"amountUnits": "100000000",
"txHash": "0xabc…",
"outputIndex": 0,
"depositAddress": "TXyz…",
"fromAddress": "TAbC…",
"blockNumber": 64512345,
"confirmations": 20,
"required": 19,
"confirmedAt": "2026-06-27T10:15:40.000Z"
}
}
FieldTypeNotes
idstring (uuid)Delivery id — your dedupe key. Also in the x-zaropay-delivery-id header.
eventstringdeposit.confirmed (or webhook.test for test probes)
createdAtstring (ISO-8601)When the payload was built
data.depositIdstring (uuid)The deposit; matches reference_id in delivery logs
data.amountstringMerchant-facing amount: the order amount when one exists, else on-chain gross
data.grossAmountstringActual on-chain amount received (always present)
data.orderAmountstring | nullExpected order amount (USDT), or null for open addresses
data.fiatCurrencystring | nullUSD if the order was priced in fiat (see Pricing in USD); else null
data.fiatAmountstring | nullThe original fiat amount (e.g. "100"); null for USDT-priced orders
data.quoteRatestring | nullThe locked USDT-per-fiat rate at order creation; null otherwise
data.txHashstring | nullOn-chain tx hash
data.depositAddress / data.fromAddressstring | nullTo / from addresses
data.confirmations / data.requirednumber | nullConfirmations at finalization
data.confirmedAtstring | nullWhen it reached CONFIRMED
Reconcile carefully

Compare data.grossAmount against data.orderAmount to detect under/overpayment. Match the payment to your order via data.orderAmount plus the order_ref you set when creating the charge. For a USD-priced order, amount / grossAmount / orderAmount are still USDT — the original dollar figure is data.fiatAmount (use it for your own books, not for payment matching).

Request headers on every delivery

HeaderValue
content-typeapplication/json
user-agentZaroPay-Webhooks/1
x-zaropay-signaturet=<unixSeconds>,v1=<hex>verify this
x-zaropay-eventthe event name
x-zaropay-delivery-idthe delivery id (= payload id)

Delivery semantics

  • Respond 2xx to acknowledge. Any other status, a timeout, or a redirect counts as failure.
  • Timeout: 10 seconds.
  • Retries: up to 5 attempts with backoff 60s → 300s → 1800s → 7200s; after the last failure the delivery is marked EXHAUSTED.
  • At-least-once: the same event can arrive more than once — dedupe on the payload id.
  • Redirects are not followed (anti-SSRF), so your endpoint must be the final URL.
  • Respond fast: acknowledge with 2xx first, then do slow work asynchronously.

Best practices

  1. Verify the signature before trusting anything — see Signature verification.
  2. Dedupe on id; make fulfilment idempotent.
  3. Acknowledge quickly (2xx), process in the background.
  4. Use the delivery logs to debug from your side.