> ## Documentation Index
> Fetch the complete documentation index at: https://docs.etherfuse.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> How webhook events drive your application's business logic — what to do when each event fires.

Webhooks are how your application **reacts to what happens at Etherfuse**. A transaction settles, a customer passes KYC, a bank account goes active — Etherfuse sends a signed `POST` to your endpoint the moment it happens, and your app does the corresponding work in your own system: credit a balance, unlock a feature, notify a user, reconcile an order.

The key mental model: **a webhook is the trigger for a state change in *your* product.** You don't poll Etherfuse asking "is it done yet?" — you let the event tell you, then act on the `status` in the payload.

<Info>
  **Webhooks vs. polling.** Webhooks fire the instant a state changes. Polling the list endpoints is slower (newly created orders take a moment to index) and wasteful (most requests return "no change"). Use webhooks for anything time-sensitive.
</Info>

## What to do on each event

This is the part most integrations care about: when event X arrives, what should your app do? Each flow below maps the lifecycle to the actions you take in your own system.

<AccordionGroup>
  <Accordion title="Onramp — customer buys tokens with MXN" icon="arrow-up-right-dots">
    Subscribe to **`order_updated`**.

    | Status      | What happened                                                            | What your app does                                                  |
    | ----------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------- |
    | `created`   | Order created; awaiting the customer's MXN deposit to the `depositClabe` | Show the customer the `depositClabe` to pay; mark the order pending |
    | `funded`    | MXN deposit received and confirmed                                       | Optionally surface "payment received, delivering tokens"            |
    | `completed` | Tokens delivered to the wallet (`confirmedTxSignature` has the tx hash)  | **Mark fulfilled, credit the user, send confirmation**              |
    | `failed`    | Order couldn't be completed                                              | Notify the customer; reconcile                                      |
    | `refunded`  | Deposit didn't match the order amount; MXN returned                      | Reconcile and tell the customer funds were returned                 |
    | `canceled`  | Canceled before funding                                                  | Close the order out in your system                                  |
  </Accordion>

  <Accordion title="Offramp — customer sells tokens for MXN" icon="arrow-down">
    Subscribe to **`order_updated`**.

    | Status      | What happened                                                | What your app does                                                                                                             |
    | ----------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
    | `created`   | `burnTransaction` is ready for the customer to sign          | Prompt the customer to sign and submit it (or use [anchor mode](/guides/testing-offramps#anchor-mode-stellar-only) on Stellar) |
    | `funded`    | The customer's transaction is confirmed on-chain             | Surface "selling, sending MXN"                                                                                                 |
    | `completed` | MXN sent to the customer's bank account                      | **Mark the payout as sent**                                                                                                    |
    | `finalized` | Reversal window has passed — funds can no longer be returned | Finalize your accounting for the order                                                                                         |
    | `failed`    | Couldn't be completed                                        | Notify the customer; reconcile                                                                                                 |
  </Accordion>

  <Accordion title="Swap — token to token" icon="right-left">
    Subscribe to **`swap_updated`**.

    | Status      | What happened                                       | What your app does                        |
    | ----------- | --------------------------------------------------- | ----------------------------------------- |
    | `created`   | `sendTransaction` is ready for the customer to sign | Prompt the customer to sign and submit it |
    | `funded`    | The source transaction is confirmed on-chain        | Surface "swap in progress"                |
    | `completed` | Target tokens delivered to the wallet               | **Update the user's balances**            |
    | `failed`    | Couldn't be completed                               | Notify the customer; reconcile            |

    **Reading the payload (not the status sequence):** `sendTransaction` is always present in the payload. It contains the transaction the customer needs to sign and submit — once signed, the field remains but is no longer actionable. On completion you get two hashes: `sendTransactionHash` (the customer's send to Etherfuse) and `receiveTransactionHash` (Etherfuse's delivery to the wallet). Which intermediate statuses fire **varies by chain** (some emit a distinct `funds_received` update, others don't), so key your logic off the **fields present in the payload** rather than expecting a particular status to arrive.
  </Accordion>

  <Accordion title="Onboarding & compliance — KYC, KYB, bank accounts" icon="user-check">
    Subscribe to **`kyc_updated`**, **`customer_updated`**, and **`bank_account_updated`**.

    | Status                                         | What happened                                                               | What your app does                                                                                                        |
    | ---------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
    | `kyc_proposed`                                 | KYC data submitted, awaiting review                                         | Show "verification pending"                                                                                               |
    | `kyc_approved` / `customer_verified`           | The customer is cleared                                                     | **Unlock their ability to transact**                                                                                      |
    | `kyc_rejected`                                 | KYC rejected (payload includes `updateReason`)                              | Block transacting; show the reason; prompt resubmission                                                                   |
    | `bank_account_updated` with `compliant: false` | The bank can't be used until the customer completes additional verification | **Launch the customer into `/idv?bank_account_id=<id>`** for that bank; see [Bank account compliance](/guides/onboarding) |
    | `bank_account_active`                          | Bank account verified and usable                                            | **Enable offramps/payouts to that account**                                                                               |

    Gating access on these events is the most common compliance pattern: don't let a customer onramp, offramp, or swap until you've received `kyc_approved` (or `customer_verified`) for them.
  </Accordion>
</AccordionGroup>

## How delivery works

<Steps>
  <Step title="Register an endpoint">
    Call [POST /ramp/webhook](/api-reference/webhooks/create-webhook) with your **HTTPS** URL and the event type. The response includes a signing **`secret`**, returned **only once** — store it securely.
  </Step>

  <Step title="Receive the event">
    When the event fires, Etherfuse sends a `POST` with the JSON payload to your URL, plus an `X-Signature` header.
  </Step>

  <Step title="Verify the signature">
    Recompute the HMAC-SHA256 signature with your secret and compare. See [Verifying Webhooks](/guides/verifying-webhooks) for Node and Python code.
  </Step>

  <Step title="Respond 2xx, then do the work">
    Acknowledge with a `2xx` immediately, then run your business logic **asynchronously**. A slow handler triggers unnecessary retries.
  </Step>
</Steps>

## Event types

| Event                  | Fires when                                   | Payload reference                                                              |
| ---------------------- | -------------------------------------------- | ------------------------------------------------------------------------------ |
| `order_updated`        | An onramp/offramp order changes status       | [order\_updated payload](/api-reference/webhooks/order_updated)                |
| `swap_updated`         | A swap changes status                        | [swap\_updated payload](/api-reference/webhooks/swap_updated)                  |
| `customer_updated`     | A customer's verification status changes     | [customer\_updated payload](/api-reference/webhooks/customer_updated)          |
| `kyc_updated`          | A programmatic KYC submission is reviewed    | [kyc\_updated payload](/api-reference/webhooks/kyc_updated)                    |
| `kyb_updated`          | A business organization's KYB status changes | [kyb\_updated payload](/api-reference/webhooks/kyb_updated)                    |
| `bank_account_updated` | A bank account's verification status changes | [bank\_account\_updated payload](/api-reference/webhooks/bank_account_updated) |

<Expandable title="Full status reference (transition diagrams + entity statuses)">
  **Onramp:**

  ```
  created → funded → completed
      ↓        ↓
    failed   refunded
      ↓
   canceled
  ```

  **Offramp:**

  ```
  created → funded → completed → finalized
      ↓        ↓
    failed   failed
      ↓
   canceled
  ```

  **Swap:**

  ```
  created → funded → completed
      ↓        ↓
    failed   failed
  ```

  **Customer statuses** (`customer_updated`): `customer_pending`, `customer_verified`, `customer_failed`

  **Bank account statuses** (`bank_account_updated`): `bank_account_pending`, `bank_account_awaiting_deposit_verification`, `bank_account_active`, `bank_account_inactive`

  **KYC statuses** (`kyc_updated`): `kyc_proposed`, `kyc_approved` (on-chain marking follows for Solana), `kyc_rejected` (includes `updateReason`)

  **KYB statuses** (`kyb_updated`): `not_started`, `awaiting_documents` (customer has documents left to submit), `awaiting_review` (submitted or has open notes, Etherfuse's turn), `approved`, `denied`
</Expandable>

## Payloads

Every webhook payload is a JSON object with a single key matching the event type; the value is the entity that changed, signed with your webhook secret (see [Verifying Webhooks](/guides/verifying-webhooks)). The full schema and a worked example for each event live on its reference page, linked from the [Event types](#event-types) table above.

Fields are omitted when `null` or not applicable to the entity's current state, so drive your logic off the fields **present** in the payload rather than expecting a fixed shape. A few specifics worth calling out:

* **`order_updated`** carries `burnTransaction`, `depositClabe`, and `confirmedTxSignature` only when relevant to the order type and status. For **embedded (non-custodial) wallets** it also carries the pending `approval` object (`approvalMessageId`, `approvalMessage`, `summary`) while the order awaits the owner's signature; there is no separate embedded-wallet transaction event. Re-read the order right before signing so the `approvalMessage` timestamp is fresh.
* **`swap_updated`** — `sendTransaction` is always present (the transaction to sign; it stays in the payload but is no longer actionable once signed); `receiveTransactionHash` appears only on completion.
* **`bank_account_updated`** — while `compliant` is `false` the customer must complete additional verification before the bank can be used; the form depends on the bank type (see [Bank account compliance](/guides/onboarding)).
* **`kyc_updated`** — on approval, `approved` is `true` and `updateReason` / `needsWork` / `userMessage` are omitted.
* **`kyb_updated`** carries the business's `organizationId` and `status`. It fires only when the status actually changes, never on individual answer edits. The `approved` / `submitted` / `notStarted` / `total` counts cover the customer-facing requirements only, and `approvedAt` is set once the business is approved.

## Reliability & best practices

* **Retries.** Failed deliveries (a non-`2xx` response or connection error) are retried up to **3 times** with **5-second** delays. Return a `2xx` promptly to avoid them.
* **Acknowledge fast, process async.** Hand the payload to a queue and return `2xx` right away; don't block the response on downstream work.
* **Handle events idempotently.** The same event can arrive more than once (e.g. a retry after your `2xx` was lost). Dedupe on the resource ID plus its status so reprocessing is harmless — this pairs naturally with the [client-generated UUIDs](/idempotency) you already use for orders.
* **Don't rely on ordering.** Drive your logic off the `status` *in the payload*, not the order in which deliveries arrive.
* **Expose a public HTTPS endpoint.** For local development, use a tunnel such as [ngrok](https://ngrok.com) so Etherfuse can reach your handler.

## Verifying signatures

Every delivery is signed so you can confirm it's authentic: `X-Signature: sha256={hex}`, computed as HMAC-SHA256 over the [RFC 8785](https://datatracker.ietf.org/doc/html/rfc8785)-canonicalized JSON body using your webhook secret. Full walkthrough with Node/Python code: **[Verifying Webhooks](/guides/verifying-webhooks)**.

## Managing webhooks

* [Create a subscription](/api-reference/webhooks/create-webhook) — `POST /ramp/webhook` (returns the one-time secret)
* [List subscriptions](/api-reference/webhooks/list-webhooks) — `POST /ramp/webhooks`
* [Get a subscription](/api-reference/webhooks/get-webhook) — `GET /ramp/webhook/{id}`
* [Delete a subscription](/api-reference/webhooks/delete-webhook) — `DELETE /ramp/webhook/{id}`

<Note>
  If a transaction expires before the customer signs it, fetch a fresh one with [POST /ramp/order/\{id}/regenerate\_tx](/api-reference/orders/regenerate-tx) or [POST /ramp/swap/\{id}/regenerate\_tx](/api-reference/swaps/regenerate-swap-tx).
</Note>
