Skip to main content
Webhooks turn the deposit and withdrawal lifecycles from a polling problem into a push-driven one. OpenFX delivers signed HTTP POSTs to an HTTPS endpoint you control the moment an event reaches a meaningful state. This page walks you from “no endpoint” to “events are verified and processed”. If you only need the signature-verification reference, jump to Webhook authentication. This page is the operational setup around that.

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 events
  • deposit.completed — a fiat or stablecoin deposit has been credited to your balance.
  • deposit.failed — a deposit attempt has reached a terminal failure state.
Withdrawal events
  • 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.
Each event delivers a single resource object in 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.
Sandbox and Live secrets are different. A handler that uses the Live secret to validate Sandbox events will reject every one. Wire the secret from environment-aware config, not a hard-coded constant.

Step 2 — Build the handler

Minimum viable handler: read the raw body, verify the signature, deduplicate by event id, dispatch.
Node / Express
Python / Flask
The signature-verification half of this is covered in full in Webhook authentication, including additional language samples and replay-attack prevention.

Step 3 — Acknowledge fast, process async

Return 2xx within a few seconds. Long-running processing (database writes, downstream API calls, sending the user an email) belongs on a queue:
OpenFX retries deliveries that don’t ack with 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:
  1. Use your Sandbox API key (sandbox_-prefixed) to select the Sandbox environment.
  2. Trigger a Sandbox deposit (your CS rep can simulate one) or a Sandbox withdrawal (initiate a small one).
  3. Verify your handler receives the signed event and returns 2xx.
  4. Check the dashboard’s delivery log for the event.
See Environments for the Sandbox-vs-Live split.

Event envelope shape

Every webhook delivery uses these top-level routing fields. This abbreviated shape collapses data 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: Use cursor pagination and walk forward from the last ID you’ve processed. Don’t poll continuously — a slow reconciliation sweep every few minutes is enough.

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. Store event.id server-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.