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

# Webhooks

> Get notified about payment status changes: registration, payload, supported events, delivery order, and retries.

<a href="/api-reference/webhooks">Tabby Webhooks</a> are HTTPS callbacks that notify you about payment-related and token-related events. You register a URL once, and Tabby sends a POST request to it whenever an event related to your account occurs — even when the customer never returns to your site. This makes webhooks the most reliable way to <a href="/pay-in-4-custom-integration/payment-processing#dont-miss-authorized-payments">catch authorized payments</a>.

## How They Work

<Steps>
  <Step title="Register an endpoint">
    <a href="/api-reference/webhooks/register-a-webhook">Register a webhook</a> for each `merchant_code` + secret key pair. The environment is determined by the key you register with: a production key (`sk_...`) registers webhooks for production payments, a test key (`sk_test_...`) — for test payments. Each pair can have up to **4 webhooks**. An optional auth header can sign the requests so you can verify their authenticity.
  </Step>

  <Step title="Receive notifications">
    Tabby sends a POST request to your URL whenever the <a href="/pay-in-4-custom-integration/webhooks#supported-events">payment status changes</a>.
  </Step>

  <Step title="Acknowledge with 200">
    Respond with a `200` HTTP status code to confirm the reception, and check the auth header to verify the request. Any other response (or no response) counts as a delivery error and triggers <a href="/pay-in-4-custom-integration/webhooks#retry-attempts">retries</a>.
  </Step>
</Steps>

## Payload

Webhooks are POST requests with a JSON body:

```JSON theme={"dark"}
{
  "id": "string",
  "created_at": "2021-09-14T13:08:54Z",
  "expires_at": "2022-09-14T13:08:54Z",
  "closed_at": "2021-09-14T13:09:45Z",
  "status": "closed",
  "is_test": false,
  "is_expired": false,
  "amount": "100",
  "currency": "SAR",
  "order": {
    "reference_id": "string"
  },
  "captures": [
    {
      "id": "string",
      "amount": "100",
      "created_at": "2021-09-14T13:09:45Z",
      "reference_id": "string"
    }
  ],
  "refunds": [
    {
      "id": "string",
      "amount": "100",
      "created_at": "2021-09-14T14:14:02Z",
      "reference_id": "string",
      "reason": "string"
    }
  ],
  "meta": {
    "order_id": null,
    "customer": null
  },
  "token": "string"
}
```

<Tip>
  Webhook payloads use lowercase statuses (`"authorized"`), while the <a href="/api-reference/payments/retrieve-a-payment">Retrieve Request</a> returns uppercase (`"AUTHORIZED"`) — this is expected. See <a href="/pay-in-4-custom-integration/payment-statuses#statuses-in-api-responses-vs-webhooks">Payment Statuses</a>.
</Tip>

## Supported Events

The payload content depends on the event:

| Event             |         Webhook payment status        | Payload update                                               |
| ----------------- | :-----------------------------------: | ------------------------------------------------------------ |
| Authorize         |               authorized              | "status": "authorized"                                       |
| Capture           |               authorized              | capture info is added to captures.\[] array                  |
| Close             |                 closed                | "status": "closed" and "closed\_at" updated                  |
| Reject            |                rejected               | "status": "rejected"                                         |
| Expire (Optional) |                expired                | "status": "expired", "expired\_at" and "is\_expired" updated |
| Refund            |                 closed                | refund info is added to refunds.\[] array                    |
| Update            | the same as before the Update Request | order.reference\_id updated                                  |

The "expire" event is optional — ask the Tabby team to enable it for your store if you want notifications when a payment is cancelled by the customer or expires.

## A Typical Payment

For a regular successful order you will receive three notifications:

1. **Payment authorized** — the payload status is `authorized`. Check the order and process it in your OMS if it wasn't processed yet, then send the <a href="/api-reference/payments/capture-a-payment">Capture Request</a>.
2. **Payment captured** — the payload status is still `authorized`, with your capture added to the `captures` array. No action is required.
3. **Payment closed** — the payload status is `closed`: the payment is completed and confirmed from both sides. No action is required.

## Best Practices

<Note>
  Webhooks are asynchronous: the delivery order is not guaranteed, and the same event may occasionally be delivered twice.
</Note>

* **Respond fast.** Acknowledge the webhook with `200` right away and process it asynchronously, instead of holding the response until processing is done.
* **Handle out-of-order delivery.** A capture event may arrive before an authorization event — use a finite state machine or similar logic instead of assuming the order.
* **Deduplicate.** Ignore a webhook if the same notification was already processed.
* **Filter events.** You receive notifications for all payment events — process only the ones you need.
* **Allowlist Tabby server IPs:**

  ```
  34.166.36.90
  34.166.35.211
  34.166.34.222
  34.166.37.207
  34.93.76.191
  34.166.128.182
  34.166.170.3
  34.166.249.7
  ```

To test and debug webhooks, use a tool like <a href="https://webhook.site/">Webhook.site</a> to inspect the payload and headers Tabby sends to your endpoint.

## Retry Attempts

A webhook request times out after **1 minute**. If it times out or gets any response other than `200`, Tabby resends it up to **4 more times** with an exponential interval between attempts (1–4 minutes). Retries don't block other notifications — Tabby keeps sending webhooks for other payment events as they occur.
