Skip to main content
Webhooks let you receive real-time notifications about payouts, deposits, customer onboarding/verification, and card transactions. When an event occurs, we send a signed POST request to your configured webhook endpoint with the event body.

Configuring your endpoint

Your endpoint must be publicly reachable over HTTPS and respond with any 2xx status code. Configure its URL with your Meru contact or via the dashboard. On creation you receive a signing secret (prefixed with whsec_) — store it securely; you need it to verify signatures.

Event structure

Every delivery body has the same top-level shape:
string
The event type (see the events below).
string
ISO 8601 timestamp of when the event was emitted.
object
The event payload. Its shape depends on type.

Events

Virtual accounts have no lifecycle events

There is no virtual_account.created, virtual_account.activated or virtual_account.deactivated event. Creating a virtual account, and its activation or deactivation, do not produce a webhook. The only signal tied to a virtual account is the deposit itself: when funds arrive, you receive a payin.updated. Use the account endpoints in the API if you need its current state.

Migrating event names

We’re standardizing some event names. During the transition, both the new and the legacy name are delivered for the same underlying event, so nothing is missed while you migrate. Each event carries a stable data.eventId — identical across the new and legacy names — so you can deduplicate the pair. The legacy names will be removed on 15 November 2026. Point your handlers at the new names before then.

Versioning and deprecation

Webhook events are versioned by event type, not by a header or a field in the payload. Your endpoint subscribes to specific event types, so keeping the version in the name is what lets you choose which version you receive.

What counts as a breaking change

Adding a new optional field to data is not breaking, and we ship it without a new version — it’s announced in the changelog. Your handler must ignore fields it doesn’t recognize. Breaking changes are removing or renaming a field, changing its type, or changing the meaning of a value. We never apply those to an existing event type. Instead we publish a new one with a version suffix — for example payin.updated.v2 — and deliver both in parallel during the deprecation window.

Deprecation policy

When we deprecate an event type or a field:
  • We announce it at least 90 days before removal, through the changelog, a notice on the event’s reference page carrying the removal date, and an email to the subscribers of the affected event.
  • The deprecated event and its replacement are delivered in parallel for the whole window, each carrying the same data.eventId so you can deduplicate the pair.
  • We don’t remove a deprecated event until its subscribers have migrated.

Delivery headers

Each request includes these headers:

Verifying signatures

Each request is signed with HMAC-SHA256 so you can confirm it came from us. The signed content is the string {webhook-id}.{webhook-timestamp}.{rawBody}, keyed with your signing secret (base64-decoded after stripping the whsec_ prefix), and the result is base64-encoded into the webhook-signature header. To verify: recompute the signature over the raw request body (before any JSON parsing) and compare it against the header in constant time. Reject deliveries whose webhook-timestamp is more than 5 minutes old.

Idempotency

The same event may be delivered more than once. Use the webhook-id header as the idempotency key: store processed IDs and skip duplicates.

Retries

If your endpoint does not return a 2xx status (or times out), we retry delivery automatically with exponential backoff over several hours, honoring Retry-After on error responses. Acknowledge quickly (under a few seconds) and process heavy work asynchronously. Endpoints that fail persistently may be disabled.

Best practices

  • Always verify the signature against the raw body before processing.
  • Respond fast with a 2xx, then process asynchronously.
  • Be idempotent using webhook-id.
  • Don’t assume order — rely on state/previousState and updatedAt, not delivery order.
  • Log the webhook-id, type, and data of every delivery.

Example handler

Testing

Expose your local server with a tunnel (e.g. ngrok) and register the public URL as your webhook endpoint:

Troubleshooting

  • Invalid signature: verify against the raw body (not re-serialized JSON), use the correct whsec_ secret, and read the webhook-id/webhook-timestamp/webhook-signature headers as-is.
  • Duplicate events: deduplicate using webhook-id.
  • Missed events: ensure your endpoint returns 2xx quickly; non-2xx responses and timeouts are retried, but persistent failures can disable delivery.
  • SSL errors: your endpoint needs a valid TLS certificate.
For help with a specific delivery, contact support with the webhook-id.