Skip to main content
This page shows you how to subscribe to events, verify that a delivery genuinely came from BMONI, and handle retries safely. Most integrations poll because they never discovered these endpoints. Almost everything worth polling for is available as an event.
Subscribe to the employee.* event names, not wallet.*. Money-movement events are renamed on their way to a partner-scoped subscription, so a subscription to wallet.deposit.completed receives nothing. See The two event families.

Subscribe

The response includes a secretKey — a 64-character hex string (32 random bytes), with no prefix:
Store secretKey immediately, in your secret manager rather than your database. Every delivery is signed with it, and a delivery you cannot verify is a delivery you must not trust.
One subscription exists per partner scope. A second POST returns 409Webhook config already exists for this partner scope. Use PATCH to update. Use PATCH /v1/webhooks/config to change the URL, the event list, or active.
PATCH cannot change partnerId. Re-scoping a subscription across partners would redirect another partner’s deliveries, so it is rejected by design.
To rotate the secret, POST /v1/webhooks/config/rotate-secret. Deliveries signed with the old secret stop verifying the moment the new one is issued, so deploy the new secret before rotating.

Verify every delivery

A delivery arrives as POST to your callbackUrl:
Compute the HMAC over the raw request body bytes, exactly as received. Parsing the JSON and re-serialising it changes key order and whitespace, which produces a different digest and a signature that never matches. Capture the raw body before your framework parses it.
Compare digests in constant time — a plain === on the hex string leaks timing information that can be used to forge a signature one byte at a time.

Retries, and what your status code means

Your response code decides whether a failed delivery is ever retried. The delivery times out after 10 seconds.
Returning 400 or 401 because your handler hit an internal error permanently discards that event. If processing fails for a reason that might succeed later — a database timeout, a downstream outage — return 5xx, never 4xx.
Because of the 10-second timeout, acknowledge first and process afterwards. A handler that finishes its work before responding will start timing out under load, and every timeout becomes a redelivery.

Deduplicate on id

Retries redeliver the same event with the same id, so handlers must be idempotent. Record the id and ignore one you have already processed.

The two event families

Money-movement events exist under two names. Which one you receive depends on your subscription’s scope.
  • Partner-scoped — the subscription carries a partnerId. Money-movement events are remapped to employee.* names and delivered only for users belonging to that partner. This is what you want.
  • Legacy global — the subscription omits partnerId. It receives the original wallet.* names for every user.
The remapping is exact:
Subscribing a partner-scoped config to wallet.deposit.completed is accepted by the API and then never fires, because the event is renamed before the subscription is matched. This is the single most likely reason a webhook integration appears silent.

Event types

Money movement

Onboarding and identity

These three replace polling GET /v1/users/{userId}/onboarding/status. Subscribing to them is the single highest-value change for an integration that currently polls, because onboarding is the longest wait in the lifecycle.

Employer linking

Cards

The legacy wallet.* names — wallet.deposit.completed, wallet.deposit.failed, wallet.deposit.refunded, wallet.withdrawal.completed, wallet.withdrawal.failed, wallet.withdrawal.processing — are accepted only by the global subscription. Do not use them in a partner-scoped one.

Inspect delivery history

When an event seems missing, check whether it was delivered before assuming it was never produced.
status is pending, delivered, or failed. When it is failed, errorMessage carries the reason — a timeout, a TLS failure, or the status code your endpoint returned. That is usually enough to tell a subscription problem from a handler problem.
There is no on-demand test trigger yet, so you cannot currently fire a synthetic event at your endpoint. Until one exists, exercise your handler by driving a real sandbox action — a small deposit produces employee.deposit.completed. Adding a test trigger is tracked as platform work.

Last reviewed: 7 August 2026.