> For the complete documentation index, see [llms.txt](https://docs.omni.integratedcommerce.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.omni.integratedcommerce.io/webhooks/webhooks.md).

# Overview

Real-time notifications for transaction, token, and merchant lifecycle events, delivered by legacy webhooks or, by invitation, by Svix.

Webhooks provide real-time notifications for completed transaction and token events. You can configure webhooks to receive notifications for specific events, such as when a sale transaction is completed or a token is created.

Each event type is separate, so you can choose to subscribe to only the events you want to receive notifications for.

Each event is sent as a `POST` request to the endpoint URLs you configure, and each endpoint chooses which event types it subscribes to. The request body contains a JSON payload with the details of the event.

{% hint style="info" %}
Configured transaction webhooks report outcomes across payment channels without depending on the customer's browser. A session or local validation error might not produce a transaction webhook. Use verified webhooks and authenticated transaction retrieval to maintain your payment records.
{% endhint %}

## Two delivery methods

Webhooks reach you by one of two delivery methods. The event payload is identical on both. Only the transport, the way you configure endpoints, and the signature differ.

```mermaid
flowchart LR
    T["Transaction or token event"] --> L["Legacy delivery<br/>x-fsk-wh-chksm"]
    T -. "if Svix is enabled for your account" .-> V["Svix delivery<br/>svix-id, svix-timestamp, svix-signature"]
    M["Merchant lifecycle event"] --> V
    L --> Y["Your endpoint<br/>same JSON payload either way"]
    V --> Y
    Y --> D["Verify, then deduplicate on event.id"]
```

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Legacy webhooks</strong></td><td>The delivery method every merchant has today. Fiska support configures your endpoints, and each delivery is signed with the <code>x-fsk-wh-chksm</code> checksum.</td><td><a href="/webhooks/legacy.md">Legacy webhooks</a></td></tr><tr><td><strong>Svix webhooks (invite only)</strong></td><td>Self-service endpoints in the partner portal, signed deliveries with replay protection, and a portal to inspect and replay failed deliveries. Available by invitation.</td><td><a href="/webhooks/svix.md">Svix webhooks (invite only)</a></td></tr></tbody></table>

{% hint style="warning" %}
**Svix webhooks are invite only.** Unless Fiska has enabled Svix for your account, you are on legacy webhooks, and that is the delivery method to build against. To ask for Svix, see [Requesting access](/webhooks/svix.md#requesting-access).
{% endhint %}

### Side by side

|                                          | Legacy webhooks                                     | Svix webhooks                                                             |
| ---------------------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------- |
| **Availability**                         | Every merchant, today                               | Invite only                                                               |
| **Who sets it up**                       | Fiska support, on your behalf                       | You, self-service, in the partner portal                                  |
| **Signature headers**                    | `x-fsk-wh-chksm`                                    | `svix-id`, `svix-timestamp`, `svix-signature`                             |
| **Signature algorithm**                  | HMAC-SHA256 over the raw body, lowercase hex        | HMAC-SHA256 over `{svix-id}.{svix-timestamp}.{body}`, base64              |
| **Secret**                               | Shared secret provided out-of-band by support       | Per-endpoint signing secret (`whsec_...`), shown in the portal, rotatable |
| **Replay protection**                    | None - the signature has no time component          | Yes - timestamp tolerance enforced by the Svix libraries                  |
| **Retries**                              | Retried on failure, but not on a published schedule | Automatic, exponential backoff over roughly a day                         |
| **Inspect and replay failed deliveries** | No - contact support                                | Yes, from the portal                                                      |
| **Events**                               | Transaction and token events                        | Transaction, token, and merchant lifecycle events                         |
| **Event payload**                        | Identical                                           | Identical                                                                 |

{% hint style="success" %}
**The payload is the same on both.** Only the transport and the signature differ. Whichever you are on, the JSON body you parse and the [event catalog](/webhooks/events.md) you handle are the same - you do not need two parsers.
{% endhint %}

## Tell from the headers

If you are not sure which delivery method your endpoint is on, capture one delivery and look at its request headers:

{% tabs %}
{% tab title="Legacy" %}

```http
POST /webhooks HTTP/1.1
Content-Type: application/json
x-fsk-wh-chksm: 862c6f2473c97472be4262b04670e777b2ed145d12d8a1c79ffc52ea9cfe7f0f
```

You are on legacy webhooks. Follow [Verifying the checksum](/webhooks/legacy/verifying-the-checksum.md), and contact support to change your endpoints.
{% endtab %}

{% tab title="Svix" %}

```http
POST /webhooks HTTP/1.1
Content-Type: application/json
svix-id: msg_2yZK3ZV4Tk3E8FQ0YB55X1N0Tq
svix-timestamp: 1783002600
svix-signature: v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=
```

You are on Svix webhooks. Follow [Verifying signatures](/webhooks/svix/verifying-signatures.md), and manage your endpoints yourself in the partner portal.
{% endtab %}

{% tab title="Both" %}
If the **same URL** receives some deliveries with `x-fsk-wh-chksm` and others with `svix-*` headers, your endpoint is registered on both delivery methods. Each event is then delivered twice - once by each.

This is expected while you move from legacy to Svix, not a fault. Handle it by deduplicating on `event.id` (see below), and ask support to remove the legacy registration once your Svix handling is verified. See [Moving from legacy to Svix](/webhooks/svix/moving-from-legacy.md).
{% endtab %}
{% endtabs %}

## Delivery and retries

Deliveries are retried on both delivery methods when your endpoint does not answer with a `2xx`. On legacy webhooks, failed deliveries are retried, but there is no published schedule and no portal to inspect or replay them - contact support. On Svix, retries follow an exponential backoff schedule over roughly the following day, endpoints that keep failing may be disabled automatically, and failed deliveries can be inspected and replayed from the portal.

{% hint style="warning" %}
Verify the delivery and save it to a durable queue before returning `2xx`, then process asynchronously. If saving fails, return a non-`2xx` response so delivery can be retried. A handler that does slow work inline risks timing out, which is treated as a failed delivery and retried - so slow processing turns into duplicate processing.
{% endhint %}

## Deduplicate on event ID

Because deliveries are retried, and because an endpoint can be registered on both delivery methods while you move from one to the other, your handler must be **idempotent** - the same event can arrive more than once.

Every payload, on either delivery method, carries an `event` envelope with a unique `id`. This excerpt shows only the envelope:

```json
{
  "event": {
    "id": "evt_01JS21X856RR8R69GV5F17XK9C",
    "type": "sale.completed",
    "timestamp": "2025-04-16T14:30:00Z",
    "merchantId": "mer_01JQZC2Y6W3F8XKT5H9DPNBMVR"
  }
}
```

Store the `event.id` of every delivery you have processed, and ignore any delivery whose `event.id` you have already seen. This one check protects you against:

* a legacy retry, or a replayed legacy delivery - the legacy checksum carries no timestamp, so this check is also its replay protection,
* Svix's automatic retries after a slow or failed acknowledgement, and a manual replay from the portal, and
* the same event arriving on both delivery methods while your endpoint is registered on both.

{% hint style="info" %}
`event.id` is the same value on both delivery methods for the same event, because both send the same payload. The Svix `svix-id` header identifies the Svix message - it is constant across retries of one Svix message, but it is not present on legacy deliveries and does not match `event.id`. Deduplicate on `event.id`.
{% endhint %}

## Where ACH depends on this

For [ACH bank transfers](/payments/ach.md), acceptance for processing is separate from settlement. The hosted ACH response reports `Processing` with result code `0`; final webhooks report `status: Completed` with the approval or decline in `transactionResponses[].responseCode`. Use transaction retrieval to reconcile a missed delivery.

## Event categories

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Transaction events</strong></td><td>Sales, authorizations, captures, refunds, and voids. Both delivery methods.</td><td><a href="/webhooks/events.md#transaction-events">Event catalog</a></td></tr><tr><td><strong>Token events</strong></td><td>Stored payment methods created and removed. Both delivery methods.</td><td><a href="/webhooks/events.md#token-events">Event catalog</a></td></tr><tr><td><strong>Merchant lifecycle events</strong></td><td>Merchant application progress, approval, and pricing decisions. Svix webhooks only.</td><td><a href="/webhooks/events.md#merchant-lifecycle-events">Event catalog</a></td></tr></tbody></table>

## Next steps

{% stepper %}
{% step %}

## Know which delivery method you are on

You are on legacy webhooks unless Fiska has enabled Svix for your account. The headers of one delivery confirm it - see [Tell from the headers](#tell-from-the-headers).
{% endstep %}

{% step %}

## Configure an endpoint

Through support on legacy webhooks, self-service in the partner portal on Svix. See [Configuring endpoints](/webhooks/legacy/configuration.md) for legacy, or [Configuring endpoints](/webhooks/svix/configuration.md) for Svix.
{% endstep %}

{% step %}

## Verify every delivery

Reject any request whose signature does not validate. See [Verifying the checksum](/webhooks/legacy/verifying-the-checksum.md) for legacy, or [Verifying signatures](/webhooks/svix/verifying-signatures.md) for Svix.
{% endstep %}

{% step %}

## Handle the events you subscribed to

See the [event catalog](/webhooks/events.md) for payloads and the full list.
{% endstep %}
{% endstepper %}
