Skip to Content
👋 Welcome to 100Pay Developers
DocsWebhooksPayment Charge Events

Payment Charge Events

Sent when a payment is received on a charge — whether it was created through the Checkout SDK, a payment link, or the create charge API. This covers both bank transfer and crypto payments.

This is a legacy payload format and has no eventType field (the x-100pay-event-type header is unknown). Identify it by the presence of chargeId together with type: "credit".

A charge with multiple payments (e.g. partial payments) produces one webhook per payment.

Payload

interface PaymentChargeWebhook { type: "credit"; // 👈 identifies the payload together with chargeId _id: string; // unique ID of this payment event chargeId: string; // the charge this payment belongs to reference: string; eventRef: string; // stable dedupe reference (equals _id) appId: string; cryptoChargeId: string; createdAt: string; // ISO 8601 data: { from: string; // sender account / address to: string; // receiving account / address transaction_id: string; status: "CONFIRMED" | "PENDING" | "FAILED"; timestamp: string; value: { local: { amount: string; currency: string }; crypto: { amount: number; currency: string }; }; metadata?: { sessionId?: string; provider?: string; // e.g. "safehaven" originalAmount?: number; fees?: number; sessionCurrency?: string; [key: string]: unknown; }; charge: { customer: { user_id?: string; name: string; email: string; phone: string }; billing: { currency: string; amount: string; description: string; country: string; vat?: number; pricing_type: "fixed_price" | "fixed_or_partial_price"; }; status: { value: "paid" | "unpaid" | "overpaid" | "underpaid"; total_paid: number; context: { status: string; value: number }; // over/underpaid delta }; ref_id: string; // your reference, set when creating the charge metadata?: Record<string, unknown>; // your metadata, echoed back payments: unknown[]; // provider-specific payment records charge_source: "external" | "payment_page"; call_back_url: string; hosted_url?: string; app_id: string; userId: string; createdAt: string; }; }; }

Example — Bank Transfer Payment

{ "type": "credit", "_id": "665f1c2e8b6a4a0012a4d901", "chargeId": "665f1b998b6a4a0012a4d8ff", "reference": "AbCdEfGhIjKlMnOp", "eventRef": "665f1c2e8b6a4a0012a4d901", "data": { "from": "0123456789", "to": "9876543210", "transaction_id": "100000000000000000000000000000", "status": "CONFIRMED", "timestamp": "2026-03-12T14:40:29.987Z", "value": { "local": { "amount": "50.00", "currency": "NGN" }, "crypto": { "amount": 50, "currency": "NGN" } }, "metadata": { "sessionId": "VIR_SESSION_0000000000000_XXXXXXXX", "provider": "safehaven", "originalAmount": 55, "fees": 5, "sessionCurrency": "NGN" }, "charge": { "customer": { "user_id": "1", "name": "Jane Smith", "email": "jane.smith@example.com", "phone": "08000000000" }, "billing": { "currency": "NGN", "vat": 0, "pricing_type": "fixed_price", "amount": "50", "description": "Funding my account", "country": "NG" }, "status": { "context": { "status": "paid", "value": 50 }, "value": "paid", "total_paid": 50 }, "ref_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "charge_source": "external", "call_back_url": "https://yourapp.com/verify-order/", "app_id": "62ee5dbfb029b7002d5b7453" } }, "appId": "62ee5dbfb029b7002d5b7453", "cryptoChargeId": "665f1b998b6a4a0012a4d8fe", "createdAt": "2026-03-12T14:40:29.998Z" }

For a crypto payment, data.value.crypto carries the on-chain amount and currency (e.g. { "amount": 10.5, "currency": "USDT" }), and data.from / data.to are blockchain addresses.

Handling

async function handlePaymentCharge(event: PaymentChargeWebhook) { const { charge, value, status } = event.data; if (status !== "CONFIRMED") return; // wait for confirmation switch (charge.status.value) { case "paid": case "overpaid": // Full amount received — fulfill the order keyed on charge.ref_id await fulfillOrder(charge.ref_id, charge.status.total_paid); break; case "underpaid": // Partial payment — charge.status.context.value holds the shortfall await recordPartialPayment(charge.ref_id, charge.status.total_paid); break; } }

Check data.charge.status.value, not just data.status. data.status: "CONFIRMED" means this payment settled; charge.status.value tells you whether the charge as a whole is paid, underpaid, or overpaid — critical when pricing_type is fixed_or_partial_price.

Before fulfilling high-value orders, confirm the payment server-side with the verify endpoint using data.transaction_id.

Deduplication

  • eventRef (and the x-100pay-event-ref header) equals the unique _id of this payment event, so each payment in a multi-payment charge is a distinct delivery.
  • Use x-100pay-delivery-id for retry-safe idempotency.
Last updated on