Best practices — Webhooks
Receive and process every onboarding and child-account event reliably and securely.
Who this section is for
Engineers building webhook handlers — both for the onboarding lifecycle on the parent and for transaction events on each activated child.
Tips
Verify signatures on every event
Every webhook PayMongo delivers is signed. Verify the signature on receipt — events without a valid signature should be rejected, even if the payload looks reasonable. Sample code and the verification rules live in Securing webhooks.
Treat handlers as idempotent
PayMongo retries failed deliveries up to twelve times with exponential backoff. Your handler must be safe to call twice on the same event. The simplest pattern: persist the event id (evt_*) on receipt and short-circuit if you have already processed it.
Return 2xx fast, process async
Webhook delivery times out quickly. Acknowledge with 2xx as soon as you have persisted the event, then process the work asynchronously (queue job, background worker). Synchronous DB writes during the request are usually fine. Anything heavier — sending email, calling other services — should happen after the response.
Register webhooks on each child for their own transactions
The events documented in Onboarding webhooks are about the onboarding lifecycle and go to the parent's endpoint. Once a child is activated and starts transacting, transaction events such as payment.paid are emitted on the child account, not the parent.
To receive a child's transaction events, register webhooks on the child using the parent's secret key and the Account-Id header:
curl --request POST \
--url https://api.paymongo.com/v1/webhooks \
--header 'Content-Type: application/json' \
--header 'authorization: Basic <base64-encoded-sk_parent_key>' \
--header 'Account-Id: org_childId' \
--data '{
"data": {
"attributes": {
"events": ["payment.paid"],
"url": "https://your-platform.example/webhooks/child-events"
}
}
}'A reasonable starter set to register on each child:
payment.paid— completed payments.payment.failed— failed payments (for reconciliation).payment.refunded— refunds applied.- Anything else relevant to your platform (payouts, wallet movements, etc.).
Coming soon — consolidated linked-transaction webhooksPayMongo is building a single webhook channel on the parent that aggregates transaction events across all linked children. When this ships, you will no longer need to register webhooks on each child for transaction events — the parent endpoint will receive them all, with the child account ID in the payload. We will publish a migration guide when it lands.
Keep separate handlers for onboarding vs transaction events
Onboarding events arrive on the parent endpoint; transaction events arrive on each child endpoint. Even if you point both at the same URL, route the event into different handlers based on attributes.type. This makes the code easier to reason about and lets you scale them independently later.
Frequently asked
Can I use one webhook endpoint for both onboarding and child transactions?
Yes. The endpoint URL can be the same; what differs is the registration scope — onboarding webhooks register on the parent, transaction webhooks register on the child (via the Account-Id header). Your handler reads attributes.type to decide what to do.
What happens if my webhook endpoint is down during an event?
PayMongo retries up to twelve times with exponential backoff. After the final retry, the event is marked failed in PayMongo's webhook logs. Use the PayMongo Dashboard to inspect missed deliveries and reconcile any gaps.
Should I rely on webhooks alone, or also poll?
Webhooks are reliable in practice, but plan for the case where one is lost or delayed. For critical state (activation status, payment status), a daily reconciliation that reads from PayMongo's APIs is a healthy backstop. Polling continuously during normal flow is unnecessary.
The activation webhook arrived but I had already created the child in my system. How do I reconcile?
The webhook is the source of truth for activation timing. Your record's status should update on the event. Idempotency on evt_* (see above) means a retry will be a no-op.
Related
- Onboarding webhooks — full payload reference for onboarding events.
- Webhook resource — registration, retries, and delivery semantics.
- Securing webhooks — signature verification.
- Linked transactions — context for acting on the child after activation.
Updated 16 days ago
