Skip to main content
Etherfuse sends a signed POST to your URL when an order, payment account, KYC or KYB status changes. Order and payment account events include the full object.
Webhooks vs. polling. Webhooks arrive when the state changes. Polling is slower and most requests return no change. Use webhooks for anything time-sensitive, and read the object with its GET endpoint when you need to confirm the current state.

Subscribe

Create one subscription per URL and event with POST /webhook_subscription:
The response is 201 and includes a secret:
We return the secret only once, in this response. Store it; you need it to verify every delivery. GET and list responses do not include it.
  • In production the URL must use https. Sandbox also accepts http.
  • A second subscription for the same URL and event returns 409 conflict.
  • GET /webhook_subscriptions lists your subscriptions. GET /webhook_subscription/{id} reads one. DELETE /webhook_subscription/{id} removes one.
  • Subscriptions belong to your organization and do not take an organization id in the route. One subscription covers your organization and every customer organization, so you do not subscribe per customer. Each payload tells you which organization the event is for.

Payload

orderUpdated and paymentAccountUpdated share one envelope:

When each event is sent

orderUpdated

  • When the order’s status changes, for example to funded, completed, failed or canceled.
  • When the order’s funding instructions arrive or are replaced. order.fundingInstructions.destination then holds where to send the funds.
Creating an order does not send a webhook. The POST /organization/{organizationId}/order response already holds the order. What your application does on each status:

paymentAccountUpdated

Sent when the account’s status changes, or when the status of any rail direction in rails changes (for example destination on spei going from pending to ready). Quote a direction only once it is ready.

kycUpdated

Sent when a customer’s KYC status changes, and when a write you sent was accepted but could not be applied. kyc.status is one of notStarted, inProgress, submitted, approved or denied. Do not let a customer transact until kyc.approved is true. When kyc.needsWork is true, kyc.userMessage says what the customer has to fix. To learn what is still outstanding, read GET /organization/{organizationId}/kyc?requirements=true when the webhook arrives. Do not poll it on a timer. A kycUpdated that moves the customer from submitted back to inProgress means the next stage has arrived and the list holds new requirements. See Tracking verification status and Requirements unlock in stages.

kybUpdated

Sent when a business customer’s KYB status changes. kyb.status uses the same values as KYC, and kyb.approvedAt is set once the business is approved.

Verify the signature

Each delivery has an X-Signature: sha256={hex} header. We compute it as HMAC-SHA256 over the RFC 8785 canonicalized JSON body. The HMAC key is your subscription’s secret, decoded from URL-safe base64.
The secret uses the URL-safe base64 alphabet (RFC 4648 section 5: - and _ in place of + and /), with = padding. It decodes to 32 bytes. Decode it with a URL-safe decoder, for example base64.urlsafe_b64decode in Python or Buffer.from(secret, "base64url") in Node.js. A standard base64 decoder fails or returns the wrong key for secrets that contain - or _.
See Verifying Webhooks for Rust, Node.js and Python code.

Delivery and retries

  • Return any 2xx to acknowledge a delivery.
  • We make up to 3 attempts per delivery, 5 seconds apart. We try again after a connection error, a timeout, a 5xx, a 408, a 429, or any other non-2xx status not in the next item.
  • We stop after the first response when your endpoint returns 400, 401, 403, 404, 405, 410 or 422.
  • We do not send an abandoned delivery later. Read the object with its GET endpoint to recover the current state.

Best practices

  • Queue events before processing. Save the payload to a queue and return 2xx promptly to avoid timeouts and retries.
  • Ignore duplicate events. Use eventId to avoid processing a retried delivery twice.
  • Check event timestamps. Deliveries can arrive out of order. Compare occurredAt before updating stored state; use the resource’s GET endpoint to confirm its current state.
  • Expose a public HTTPS endpoint. For local development, use a tunnel such as ngrok so we can reach your handler.

Migrating from v1

v1 and v2 subscriptions are separate. A v1 subscription keeps receiving the v1 payload and does not appear in GET /webhook_subscriptions. To receive the v2 payloads, create orderUpdated, paymentAccountUpdated, kycUpdated and kybUpdated subscriptions. See the V1 → V2 guide.