Prerequisites
- A publicly reachable HTTPS endpoint (no plain HTTP — signatures protect integrity, TLS protects in-transit confidentiality)
- An admin user in the OpenFX dashboard
- A handler that can hold a small amount of state (deduplication of replayed events)
Step 1 — Register your endpoint
1
Open the dashboard
Sign in to app.openfx.com/webhooks, your organization’s Webhooks settings.
2
Add a webhook URL
Enter your HTTPS URL. The dashboard supports one webhook URL per environment (Live and Sandbox have separate registrations — see Environments).
3
Subscribe to event types
OpenFX delivers five event types across two resource families. Every event type name follows a dotted, lowercase
{resource}.{action} convention, so you can match a whole family by prefix (e.g. withdrawal.*):Deposit eventsdeposit.completed— a fiat or stablecoin deposit has been credited to your balance.deposit.failed— a deposit attempt has reached a terminal failure state.
withdrawal.processing— a withdrawal has been accepted and is moving through the payment rail.withdrawal.completed— a withdrawal has settled successfully.withdrawal.failed— a withdrawal has reached a terminal failure state.
data — the Deposit or Withdrawal in its current state. Subscribe to whichever events your integration needs; if in doubt, subscribe to all five.4
Copy the signing secret
The dashboard generates a per-org signing secret on first registration. Store it as
OPENFX_WEBHOOK_SECRET in your secrets manager. Treat it like a database password — anyone with it can mint events that pass signature verification.Step 2 — Build the handler
Minimum viable handler: read the raw body, verify the signature, deduplicate by eventid, dispatch.
Node / Express
Python / Flask
Step 3 — Acknowledge fast, process async
Return2xx within a few seconds. Long-running processing (database writes, downstream API calls, sending the user an email) belongs on a queue:
2xx. The combination of “ack fast” + “process via queue” + “deduplicate by event id” makes retries safe.
Step 4 — Test in Sandbox
The fastest path to a working integration is to fire events from Sandbox before going to Live:- Use your Sandbox API key (
sandbox_-prefixed) to select the Sandbox environment. - Trigger a Sandbox deposit (your CS rep can simulate one) or a Sandbox withdrawal (initiate a small one).
- Verify your handler receives the signed event and returns
2xx. - Check the dashboard’s delivery log for the event.
Event envelope shape
Every webhook delivery uses these top-level routing fields. This abbreviated shape collapsesdata to an empty object and omits the required Deposit or Withdrawal fields; use the complete, schema-checked event examples on the payload pages when implementing a handler.
Field names are
camelCase; monetary amounts are strings (e.g. "1000.00"); timestamps are RFC 3339 with millisecond precision and a trailing Z. This matches the v3 REST API conventions exactly.
The field-level reference is in:
Polling fallback
Webhooks are the primary delivery channel, but if your handler is briefly unreachable, OpenFX retries. As a defense-in-depth, periodically reconcile against the read endpoints:GET /v3/fx/depositsfor missed deposit events — pair with Deposit lifecycle for the state model and reconcilable fieldsGET /v3/fx/withdrawalsfor missed withdrawal events — use the adaptive-backoff pattern on the Withdrawal lifecycle page so a slow rail doesn’t burn your rate-limit budget
Common mistakes
- Parsing the body before verifying. JSON re-serialization changes whitespace and key order, which changes the HMAC digest. Always verify against the raw request body.
- String comparison on signatures. Use a constant-time comparison (
crypto.timingSafeEqual,hmac.compare_digest). Standard===leaks timing. - Not checking the replay window. Verify the
t=timestamp in the signature header and reject events older than 5 minutes to prevent replay attacks. - Not deduplicating by event
id. A handler that 500s on the first delivery and 200s on the redelivery will receive the same event twice. Storeevent.idserver-side and short-circuit duplicates. Retain event IDs for at least 48 hours — OpenFX retries failed deliveries on a backoff curve within that window; older IDs can be safely purged. - Long-running work before acking. A 30-second database write turns into a webhook timeout, which turns into a retry, which lands you with three copies of the same work in flight. Ack first, process async.
- Mixing Sandbox and Live secrets. Wire the secret from environment config, not a constant.
What’s next
Webhook authentication
Signature scheme + verification code samples.
Deposit webhooks
Event shape for fiat and stablecoin deposits.
Withdrawal webhooks
Event shape for withdrawal completion and failure.
Verified accounts
Pre-configure withdrawal accounts the webhooks will report on.