Skip to Content
👋 Welcome to 100Pay Developers
DocsWebhooks

Webhooks

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

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

Setup

In your Developer Settings , configure:

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

Verifying Webhook Signatures

Every webhook request includes a x-webhook-secret (or similar) header. Always verify this on your server before processing the event.

// Express.js example app.post("/api/webhooks/100pay", (req, res) => { const incomingSecret = req.headers["x-webhook-secret"]; if (incomingSecret !== process.env.WEBHOOK_SECRET) { return res.status(401).json({ error: "Unauthorized" }); } const event = req.body; handleWebhookEvent(event); res.status(200).json({ received: true }); });

Never skip signature verification. Without it, anyone could send fake events to your endpoint.

Event Types

EventTrigger
bank_transfer.debitOutgoing bank transfer was initiated from your account
bank_transfer.creditIncoming bank transfer was received into your account
wallet.depositCrypto deposit received to a wallet address
wallet.deposit.internalInternal transfer received between 100Pay wallets

Payload Schemas

bank_transfer.debit — Outgoing Transfer

{ "eventType": "bank_transfer.debit", "transactionId": "txn_abc123", "reference": "PAY_1709500000000", "amount": 50000, "timestamp": "2026-03-03T22:00:00Z", "transaction": { "status": "Successful", "from": "your_account", "to": "1234567890", "metadata": { "paymentReference": "PAY_1709500000000", "description": "Payment for services", "fees": 100 } }, "data": { "sessionId": "sess_abc123", "creditAccountName": "JOHN DOE", "creditAccountNumber": "1234567890", "status": "Successful", "narration": "Payment for services", "fees": 100 } }

bank_transfer.credit — Incoming Transfer

{ "eventType": "bank_transfer.credit", "transactionId": "txn_xyz789", "amount": 25000, "timestamp": "2026-03-03T22:05:00Z", "data": { "sessionId": "sess_xyz789", "debitAccountName": "JANE SMITH", "creditAccountNumber": "0987654321", "status": "Successful", "narration": "Transfer from Jane", "fees": 0 } }

wallet.deposit — Crypto Wallet Deposit

{ "eventType": "wallet.deposit", "transactionId": "txn_crypto_001", "amount": "50.00", "symbol": "USDT", "network": "bsc", "timestamp": "2026-03-03T22:10:00Z", "data": { "address": "0xABC...DEF", "transactionHash": "0xhash...", "confirmations": 12, "status": "completed" } }

wallet.deposit.internal — Internal Wallet Deposit

{ "eventType": "wallet.deposit.internal", "transactionId": "txn_internal_001", "amount": "100.00", "symbol": "USDT", "timestamp": "2026-03-03T22:15:00Z", "data": { "from": "sender@payid", "to": "recipient@payid", "note": "Invoice payment", "status": "completed" } }

Handling Events

async function handleWebhookEvent(event: { eventType: string; [key: string]: unknown }) { switch (event.eventType) { case "bank_transfer.debit": // Outgoing transfer confirmed await updateTransferRecord(event.transactionId, "completed"); break; case "bank_transfer.credit": // Incoming payment received await creditUserAccount(event.amount); break; case "wallet.deposit": // Crypto deposit confirmed await handleCryptoDeposit(event); break; case "wallet.deposit.internal": // Internal PayID transfer received await handleInternalDeposit(event); break; default: console.log("Unhandled event type:", event.eventType); } }

Transfer Status Lifecycle

For bank transfers, status progresses through the following states:

StatusDescription
"Created"Transfer queued, not yet submitted to bank
"Processing"Submitted to the bank, awaiting confirmation
"Successful"Funds delivered to recipient
"Failed"Transfer failed — check data.failureReason

Best Practices

  • Respond with 200 quickly — process events asynchronously if needed. If your server takes too long, 100Pay may retry.
  • Handle duplicate events — webhooks may be delivered more than once. Use the transactionId to deduplicate.
  • Log all incoming events — keep raw payloads for debugging and audit trails.
  • Use HTTPS — webhook endpoints must use HTTPS to ensure payload integrity.

Retry Policy

If your endpoint returns a non-2xx response or times out, 100Pay will retry the webhook. Implement idempotent handlers using transactionId to safely handle retried events.

Next Steps

Last updated on