This page covers all webhook events, which span multiple platform areas (transaction activity, deposits, widget flows, and OAuth account linking) rather than belonging to any single feature.
How it works
- You register a webhook URL on your application in the Developer Portal.
- You select which event types to listen for (or subscribe to all of them).
- When a matching event occurs, Fluz sends an HTTP
POSTto your URL with a signed JSON payload. - Your server verifies the signature, acknowledges with a
2xx, and processes the event.
Webhooks by app type
How events are routed to your app depends on your application model.Private apps — your own account
Webhooks fire for activity on your own account. Any transaction, decline, or deposit on accounts you own triggers webhooks registered on your app. Common use cases: notifications when a virtual card transaction is authorized or settled; real-time alerting on declines; monitoring deposits and balance changes on your spend accounts.Public apps (OAuth) — acting on behalf of users
Webhooks fire for activity on accounts that have authorized your app. When a user grants your app access via OAuth, their events are routed to your registered webhooks — provided the user’s OAuth grant includes the required scopes. This works whether the user interacts through your direct API integration or an embedded widget; the key requirement is an OAuth relationship between the user and your app. Common use cases: knowing when a user has linked (or re-linked) their account to your app; monitoring virtual card spend across connected accounts; real-time decline notifications; tracking deposit completions; receiving KYC status updates.Widget apps
Widget apps are a specialized public app. Events route the same way (based on the OAuth relationship), and widget apps additionally support widget-specific events like transfer completions and gift card purchases.Event types
Fluz only delivers an event if your application — and, for public/OAuth apps, the individual user’s OAuth grant — holds the required scopes. Events with no required scopes (such asOAUTH_USER_LINKED) are delivered to any subscribed public/OAuth app.
Transaction events
Cover the full transaction lifecycle. Apply to all app types.Deposit events
OAuth events
Apply to public/OAuth and widget apps.OAUTH_USER_LINKED is delivered only to public/OAuth app types — private-app subscribers do not receive it. Because it has no scope requirement, you receive it for every user who links or re-links to your app, regardless of which scopes they grant. Use it to provision or update your local record of the connected user, capture the granted scope set, and map your own user identifier via externalReferenceId.
Widget-specific events
Apply to widget and OAuth integrations where users interact through Fluz-embedded flows.📘 Naming note:WIDGET_DEPOSIT_COMPLETEandWIDGET_WITHDRAW_COMPLETEdescribe customer↔app transfers; theDEPOSIT/WITHDRAWwording is historical. Treat “deposit” as “customer → app” and “withdraw” as “app → customer.”
Setting up webhooks
1. Open the Developer Portal
Go to the Developer Portal and select your application.2. Open the Webhooks section
- OAuth apps: OAuth tab → Webhook URLs.
- Widget apps: Widget tab → Webhook URLs.
- API / private apps: the Webhook URLs section in your app settings.
3. Add a webhook URL
Click Add new URL and enter your HTTPS endpoint (e.g.,https://api.yourapp.com/webhooks/fluz).
4. Select events
Choose the event types you want to receive.5. Save
Click Create Webhook. Your endpoint begins receiving events immediately.Managing webhooks
- Multiple endpoints — you can register more than one webhook URL per application.
- Change subscribed events — delete the webhook and recreate it with the new event selection.
- Remove a webhook — click Remove next to it. The webhook is archived immediately and stops receiving events.
Receiving webhooks
Request format
Every webhook is delivered as an HTTPPOST with these headers:
The body is a JSON object, and every payload includes an
eventType field identifying the event.
Endpoint requirements
- HTTPS only — plain HTTP endpoints are rejected at registration time.
- Publicly accessible and able to accept
POSTrequests. - Respond with a
2xxwithin 30 seconds. Non-2xxresponses or timeouts trigger retries. - Verify the HMAC signature on every request.
Verifying signatures
Every delivery includes anX-HMAC-Signature header — an HMAC-SHA256 hash of the raw JSON body, signed with your application’s API key. Always verify it before trusting a payload.
⚠️ Verify against the raw request body. Compute the HMAC over the exact bytes Fluz sent — do not re-serialize the parsed JSON. Re-stringifying can reorder keys or change whitespace and cause valid signatures to fail. The examples below capture the raw body for this reason.
Node.js (Express)
Python (Flask)
Responding to webhooks
Your endpoint must:- Respond with a
2xxstatus within 30 seconds. - Return quickly — acknowledge first, then process asynchronously.
- Be reachable over HTTPS. Your endpoint must not:
- Respond with redirects (
3xx). - Respond with
4xx/5xxfor valid webhooks (this triggers retries).
Retry policy
New events resume delivery automatically once your endpoint recovers. To re-send events whose retries were already exhausted, contact support with the relevant
X-Event-ID.
Idempotency & ordering
Webhooks may be delivered more than once, and delivery order is not guaranteed.- Deduplicate using the
X-Event-IDheader. Persist processed IDs (Redis or a database in production) and skip events you’ve already handled. - Order by data, not arrival. If sequence matters, order by payload timestamps (
createdAt,updatedAt,transactionDateTime) and event IDs.
Identifying the app and user
- User:
userIdis the Fluz user ID. For OAuth/widget events,externalReferenceIdmaps to your user identifier from the OAuth flow. - App: transaction payloads include
connectedAppIdandconnectedAppName. If you route multiple apps to one endpoint, branch onconnectedAppId. ForOAUTH_USER_LINKED, the app is identified byappId.
Payload reference
Every payload includes aneventType. Field availability can vary by event; handlers should ignore unrecognized fields for forward compatibility.
📘 A note onstatusvalues. For created/updated transactions thestatusfield takes one ofPENDING,SETTLED, orFAILED. Declined transactions carry astatusofDECLINED(orFAILED). Earlier drafts of this page showedCOMPLETEDas a status — that value is not emitted; useSETTLEDto detect a finalized transaction.
Transaction created (TRANSACTION_CREATE)
Fired for any new transaction — virtual card purchases, deposits, transfers, and more.
Transaction updated (TRANSACTION_UPDATE)
Fired when a transaction’s status or details change — for example, when a pending authorization settles.
TRANSACTION_CREATE where present, plus updatedAt (ISO 8601) marking when the change occurred. userId, connectedAppId, and connectedAppName are carried through the same way as on TRANSACTION_CREATE, so you can identify the user and app consistently across the lifecycle. A status transition to SETTLED is the signal that a previously pending transaction has finalized.
Transaction declined (TRANSACTION_DECLINE)
Fired when a transaction is declined. Includes structured decline reasons your app can act on.
Additional optional fields may be present depending on the transaction (e.g.
merchantId, merchantCity, merchantState, merchantCountry, cardDisplayName, virtualCardProgram, channel, bankAccountNickname, bankAccountLastFour). Ignore any you don’t use.
Deposit complete (DEPOSIT_COMPLETE)
Fired when a deposit from a funding source to a spend account completes.
OAuth user linked (OAUTH_USER_LINKED)
Fired when a user completes OAuth linking to your application — on both the initial link and on subsequent updates (for example, when the user re-authorizes with a different set of scopes). Public/OAuth and widget apps only. No scope is required to receive this event.
Because
OAUTH_USER_LINKED fires again on re-link/updates, treat it as an upsert: create the connected user on first receipt, and refresh the stored scope set on later deliveries.
KYC initiation (WIDGET_KYC_INITIATION)
Fired when a user begins identity verification. Public/widget apps only.
Transfer completed — customer to app (WIDGET_DEPOSIT_COMPLETE)
Fired when a user transfers funds to your application.
Transfer completed — app to customer (WIDGET_WITHDRAW_COMPLETE)
Fired when your application transfers funds to a user.
Gift card purchase (WIDGET_PURCHASE_GIFT_CARD)
Fired when a gift card purchase completes through the widget.
userId (Fluz user ID), accountId (Fluz account ID), externalReferenceId (your user identifier from the OAuth flow), and amount where applicable.
Best practices
- Respond fast, process later. Return
200immediately and handle the event asynchronously to avoid timeouts and unnecessary retries. - Verify every request. Validate
X-HMAC-Signatureagainst the raw body using your API key before processing. - Deduplicate with event IDs. Track
X-Event-IDto handle retried/duplicate deliveries. - Treat enum-like fields as open strings. New
transactionType,channel, anddeclineCategoryvalues can appear over time; branch on the values you care about and tolerate unknowns. - Upsert on
OAUTH_USER_LINKED. It can fire more than once per user; refresh the stored scope set each time rather than assuming first-link-only semantics. - Accept unknown fields. Payloads may gain fields over time; ignore unrecognized ones rather than failing.
- Monitor your endpoint. Alert on repeated non-
2xxresponses — after 5 failed attempts an event’s delivery is abandoned.
Troubleshooting
Need help?
- Technical issues: check your endpoint logs and contact support with the
X-Event-ID. - Scope questions: see Application Scopes and Decline Codes.