Skip to Content
đź‘‹ Welcome to 100Pay Developers
DocsWebhooksWebhooks

Webhooks

100Pay sends webhook events to your server in real time when something happens in your account — a payment is received, a transfer completes, a deposit comes in, a transaction changes status, or a customer record is updated. Use webhooks to trigger order fulfillment, update records, or notify users.

Your webhook endpoint must be publicly accessible over HTTPS (not localhost). Register it in your Developer Settings .

Setup

In your Developer Settings , configure one or more webhook endpoints. Each endpoint has:

FieldValue
Webhook URLYour server endpoint, e.g. https://yourapp.com/api/webhooks/100pay
Verification TokenA per-endpoint secret used to verify that events are genuinely from 100Pay

You can register multiple endpoints — every event is delivered independently to each active endpoint. Duplicate URLs are automatically de-duplicated.

Event Catalog

EventTriggerDetails
Payment charge (no eventType — see below)A payment on a charge is confirmed (checkout, payment link, or API charge)Payment events
transaction.depositA wallet credit transaction was recordedTransaction events
transaction.withdrawalA wallet debit transaction was recordedTransaction events
transaction.status.changedA transaction moved between statuses (e.g. pending → successful)Transaction events
reward.payoutA reward was paid to a walletTransaction events
bank_transfer.debitAn outgoing bank transfer changed statusBank transfer events
bank_transfer.creditAn incoming bank transfer was receivedBank transfer events
wallet.depositAn on-chain crypto deposit arrived at a wallet addressWallet deposit events
wallet.deposit.internalAn internal transfer between 100Pay wallets was receivedWallet deposit events
transaction_session.completed / transaction_session.expiredA payment session finished or timed outSession callbacks
customer.* (15 events)Customer lifecycle, virtual bank account, and deposit eventsCustomer events

Delivery Headers

Every webhook request is an HTTP POST with Content-Type: application/json and the following headers:

HeaderDescription
verification-tokenThe endpoint’s verification token — compare against your stored secret
x-100pay-event-typeThe event type (e.g. transaction.deposit). unknown for legacy payment-charge payloads
x-100pay-event-refA stable reference for the underlying resource (transaction ID, charge ID, session ID, …)
x-100pay-delivery-idUnique ID for this logical delivery — stable across retries. Use it for idempotency
idempotency-keySame value as x-100pay-delivery-id
x-100pay-webhook-idThe ID of the endpoint the event was delivered to
x-100pay-attempt-number1 for the initial delivery, incremented on each retry

Customer events additionally include x-100pay-event-id, x-100pay-timestamp, and an HMAC signature header x-100pay-signature.

Verifying Webhooks

Always verify the verification-token header before processing an event:

// Express.js example app.post("/api/webhooks/100pay", express.json(), (req, res) => { const token = req.headers["verification-token"]; if (token !== process.env.PAY100_VERIFICATION_TOKEN) { return res.status(401).json({ error: "Unauthorized" }); } // Acknowledge fast — process asynchronously res.status(200).json({ received: true }); handleWebhookEvent(req.body).catch(console.error); });

Never skip verification. Without it, anyone could send fake events to your endpoint. For customer.* events, verify the HMAC signature as well — see Customer events.

For defense in depth, verify critical state server-side (e.g. call the payment verification API) before releasing goods or crediting balances.

Routing Events

Most payloads carry an eventType field. The legacy payment-charge payload does not — identify it by the presence of chargeId:

async function handleWebhookEvent(event: Record<string, any>) { switch (event.eventType ?? event.event) { case "transaction.deposit": case "transaction.withdrawal": case "reward.payout": return handleTransaction(event); case "transaction.status.changed": return handleStatusChange(event); case "bank_transfer.debit": case "bank_transfer.credit": return handleBankTransfer(event); case "wallet.deposit": case "wallet.deposit.internal": return handleWalletDeposit(event); case "transaction_session.completed": case "transaction_session.expired": return handleSession(event); default: if (event.eventType?.startsWith("customer.") || event.type?.startsWith("customer.")) return handleCustomerEvent(event); // Legacy payment charge payload — no eventType field if (event.chargeId && event.type === "credit") return handlePaymentCharge(event); console.log("Unhandled event:", event.eventType ?? event.type); } }

Idempotency & Duplicate Suppression

100Pay reserves a delivery record before any outbound request, so the same logical event is sent at most once per endpoint — but network conditions mean your endpoint may still occasionally see duplicates (e.g. your 200 response was lost and the delivery was retried).

  • Store x-100pay-delivery-id and skip events you’ve already processed. It is stable across retries of the same delivery.
  • As a fallback, x-100pay-event-ref (or the payload’s transactionId / chargeId) identifies the underlying resource.
  • Some events legitimately fire once per state transition — e.g. bank_transfer.debit fires again when a transfer moves from Created to Successful, and transaction.status.changed fires per transition. Each transition has a distinct delivery ID.

Retries

If your endpoint returns a non-2xx response or times out (15 seconds), delivery is retried with back-off:

AttemptDelay after previous failure
1immediate
21 minute
35 minutes
430 minutes

After 4 failed attempts the delivery is marked exhausted and will not be retried. customer.* events use their own retry loop (exponential back-off, 30s–1h) with a 72-hour delivery deadline.

Best Practices

  • Respond 2xx within 15 seconds — acknowledge first, then process asynchronously (queue, background job).
  • Be idempotent — key your processing on x-100pay-delivery-id.
  • Verify the verification-token header (and HMAC for customer events) on every request.
  • Log raw payloads for debugging and audit trails.
  • Don’t rely on ordering — events can arrive out of order, especially across retries. Use timestamp / previousStatus fields to reconcile.
  • Use HTTPS with a valid certificate.

Next Steps

Last updated on