Guides
Guides/Webhooks

Webhooks

Webhooks allow you to receive real-time notifications about important events related to outbound and inbound transactions in your FV Merchant account, eliminating the need to continuously poll APIs.


📡 How Webhooks Work

When an event occurs (e.g., payment status update or deposit confirmation), FV Bank sends an HTTP POST request to your configured webhook URL with event details.

code

Event Occurs → FV System → Webhook POST → Your Server → Process Event

⚙️ Webhook Setup

To receive webhooks, you must provide a publicly accessible HTTPS endpoint.

  • The endpoint must accept POST requests
  • It should return a 200 OK response to acknowledge successful receipt
  • Respond quickly (acknowledge first, process asynchronously)

📦 Payload Structure

Every webhook body has the same envelope:

code

json
{
  "Date": "2025-01-14T09:12:33.000Z",
  "Event": "TRANSACTION_STATUS_UPDATED",
  "Id": "7bdeb64f-50b2-4624-87ea-4fa111282e8e",
  "Message": "The member transaction status changed",
  "Data": {
    "TransactionNumber": "FV000006187",
    "Status": "COMPLETE",
    "Amount": "1000.00",
    "Currency": "USD"
  …
FieldDescription
IdUnique delivery ID. Use this to deduplicate — the same Id may arrive more than once
EventEvent type (e.g. TRANSACTION_CREATED, TRANSACTION_STATUS_UPDATED, DEPOSIT_RECEIVED)
DateTime the event was queued
MessageHuman-readable description of the event
DataEvent payload. This object alone is what the signature covers

🔐 Webhook Security (Signature Verification)

Each webhook request includes an HMAC signature header:

code

x-signature: <hex-encoded HMAC-SHA256>
PropertyValue
Header namex-signature
AlgorithmHMAC-SHA256
EncodingLowercase hex
SecretYour API client secret — the same secret you use to sign the X-AUTH-TOKEN JWT. There is no separate webhook secret
Signed bytesJSON.stringify(body.Data) — the serialized Data object only, not the full request body and not the raw request bytes

Important for non-Node.js integrations: the signature is computed over a re-serialization of the Data object, not over the raw bytes we transmit. To verify, parse the JSON body, then re-serialize body.Data using a serializer that preserves the original key order and uses compact separators (no extra whitespace). In languages where JSON object key order is not preserved by default, you must preserve insertion order explicitly, otherwise the computed HMAC will not match.

How to verify:

  1. Take your API client secret
  2. Compute the HMAC-SHA256 of the re-serialized Data object
  3. Compare it to the x-signature header using a constant-time comparison

Example (Node.js):

code

javascript
const crypto = await import('crypto');
const signature = req.headers['x-signature'];
const hmac = crypto.createHmac('sha256', CLIENT_SECRET);
const expectedSignature = hmac.update(JSON.stringify(req.body?.Data)).digest('hex');
const a = Buffer.from(signature);
const b = Buffer.from(expectedSignature);
if (a.length === b.length && crypto.timingSafeEqual(a, b)) {
    console.log('Matched');
} else {
    console.log('Signature Not Matched');
  …

Replay protection

Webhook requests do not include a timestamp header, and the signature does not cover a timestamp or nonce. A captured request stays valid indefinitely if replayed. Because of this:

  • Always serve your webhook endpoint over HTTPS
  • Deduplicate on Id and ignore any delivery you have already processed
  • Treat the webhook as a notification, not as the source of truth: re-read the authoritative state with GET /v2/transactions/details/{transactionNumber} before acting on high-value changes
  • Optionally restrict your endpoint to FV Bank source IPs (contact support for the current list)

🔁 Delivery and Recovery

Each event is delivered on a single attempt when it is generated. If your endpoint is unreachable or returns a non-2xx response, the delivery is recorded as failed and is not automatically retried in production. Plan for this explicitly:

  • Reconcile by polling. Use GET /v2/transactions/details/{transactionNumber} for a known transaction, or POST /v2/transactions/history and POST /v2/transactions/history/completed to sweep a date range after an outage.
  • Re-request a callback. POST /v2/transactions/send-webhook re-sends a webhook for a specific transaction. It delivers the current state of that transaction, not the individual event you missed — if several status changes occurred while you were down, you receive only one callback reflecting the latest state.
  • Make your handler idempotent. Deduplicate on Id, and make status transitions safe to apply more than once.

Ordering is not guaranteed. Do not assume webhooks arrive in the order the underlying status changes occurred; always trust the latest state read from the API over the arrival order of callbacks.


📌 Best Practices

  • Verify the x-signature before processing
  • Store processed Id values to prevent duplicate processing
  • Use webhooks for real-time updates and the transaction APIs for reconciliation
  • Always return 200 OK quickly, then process asynchronously
  • Alert on gaps: if you expect a terminal status and never receive one, poll rather than wait

📌 Summary

Webhooks provide a low-latency signal for:

code

Payments → Status Updates
Deposits → Confirmation Events

They are the fastest way to learn about a change, but they are best-effort. A correct integration pairs webhooks with periodic reconciliation against the transaction APIs.

API reference

Search guide books, endpoints, paths, or parameters

↑↓navigate↵openEscclose