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

# Webhook Notifications

> StablePay Webhook event types, request headers, signature verification requirements, and handling recommendations for the frozen status in payment.failed events.

StablePay pushes critical payment, refund, subscription, and invoice events asynchronously to your callback URL configured in the Merchant Dashboard via Webhooks.

This page summarizes Webhook event types, request headers, signature verification requirements, and recently added adaptations for the risk control freeze status.

## Configuring Webhooks in the Merchant Portal

Before your server can receive Webhook notifications, you need to create an API store and configure the callback address in the [Stores and Developers](https://merchant.stablepay.co/#/merchant/stores) page.

### Step 1: Create an API Store

1. Open the [Stores and Developers](https://merchant.stablepay.co/#/merchant/stores) page.
2. Click **Create Store**.
3. Select **API** as the channel type.
4. Fill in the store name and domain according to your actual business information.
5. Submit the application and wait for StablePay review.

![Create an API store in the Stores and Developers page](https://static.stablepayfi.ai/developer.jpeg)

### Step 2: Configure Webhook after Store Activation

Once the store status changes to **Enabled**:

1. Return to the [Stores and Developers](https://merchant.stablepay.co/#/merchant/stores) page.
2. Open the action menu for the corresponding store.
3. Click **Webhook**.
4. Fill in your Webhook callback URL and save.
5. If needed, you can also click **Keys** in the same action menu to create the API Key and Secret Key required for server-side integration.
6. If the validity period is set to `0 days`, it means the key is valid indefinitely.

![Create keys and configure Webhook URL in the action menu of an enabled store](https://static.stablepayfi.ai/webhook.jpeg)

<Note>
  It is recommended to use an HTTPS server address accessible from the StablePay public network as the Webhook callback address, separate from browser pages or frontend routes. Please do not use frontend page URLs directly as Webhook target addresses.
</Note>

## Event Types

| Category     | Event Type                                                                                                                                                                          |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Payment      | `payment.completed`, `payment.failed`, `payment.expired`, `payment.cancelled`                                                                                                       |
| Refund       | `refund.succeeded`, `refund.failed`                                                                                                                                                 |
| Subscription | `subscription.created`, `subscription.trialing`, `subscription.active`, `subscription.past_due`, `subscription.canceled`, `subscription.updated`, `subscription.incomplete_expired` |
| Invoice      | `invoice.created`, `invoice.paid`, `invoice.payment_failed`                                                                                                                         |

## Webhook Request Headers

| Header                   | Description                                                                                  |
| ------------------------ | -------------------------------------------------------------------------------------------- |
| `Content-Type`           | Fixed `application/json`                                                                     |
| `User-Agent`             | `StablePay-Webhook/1.0`                                                                      |
| `X-StablePay-Signature`  | Lowercase hex signature of `{timestamp}.{nonce}.{raw_body}` using HMAC-SHA256                |
| `X-StablePay-Timestamp`  | Unix timestamp (seconds)                                                                     |
| `X-StablePay-Nonce`      | Random string (16-64 characters), included in signature calculation, unique per notification |
| `X-StablePay-Event-Type` | Event type, e.g., `payment.completed`                                                        |
| `X-StablePay-Event-ID`   | Unique event ID for idempotency and deduplication                                            |

## Signature Verification

Please use the raw request body bytes for verification; do not parse JSON and then re-serialize before verification.

The signature string format is as follows:

```text theme={"system"}
sign_payload = {timestamp} + "." + {nonce} + "." + {raw_body}
```

Verification recommendations:

1. Read `X-StablePay-Signature`, `X-StablePay-Timestamp`, and `X-StablePay-Nonce`
2. Verify the timestamp is within 5 minutes of the current time
3. Verify the nonce length is 16-64 characters
4. Use your Secret Key to generate an HMAC-SHA256 of the signature string
5. Use constant-time comparison to verify the signature

> For signature verification code examples, refer to [Pre-integration Preparation](/zh/api/getting-started).

## Response and Retry

* Your service must return HTTP `2xx` within **30 seconds**
* Returns of `429` or `5xx` will enter the retry queue; other `4xx` responses will not retry by default
* Maximum of **10 retries**
* It is recommended to use `X-StablePay-Event-ID` or the `id` field in the request body for idempotency and deduplication

## payment.failed and frozen Status

StablePay has recently enhanced its risk control capabilities. After this update, when a payment order triggers high-risk control policies, funds may be temporarily frozen. Such scenarios typically involve suspected illicit activities, abnormal transactions, or other high-risk behaviors, requiring further review before confirming subsequent processing results.

After this mechanism goes live, StablePay **will continue to use the existing `payment.failed` webhook event type** to notify merchant systems, but the callback will include a new payment status:

```json theme={"system"}
"status": "frozen"
```

This means `data.object.status` in `payment.failed` events now has at least two processing branches:

* `failed`: Normal payment failure; handle according to existing failure logic
* `frozen`: Risk control freeze; should not be treated as a normal payment failure

Depending on the channel or integration source, business extension fields may appear in `metadata` or as independent fields. Please refer to `type`, `id`, and the core status fields in `data.object` that are directly relevant to your business processing.

### frozen Example Callback

```json theme={"system"}
{
  "id": "evt_1778818565572372433",
  "data": {
    "object": {
      "amount": 1,
      "status": "frozen",
      "currency": "USDT",
      "metadata": {
        "source": "api",
        "wc_order_id": "order_0b543c39"
      },
      "order_id": "order_0b543c39",
      "session_id": "20260515110100101649130000272078",
      "exchange_rate": ""
    }
  },
  "type": "payment.failed",
  "created_at": 1778818549
}
```

### Handling Recommendations

When receiving a `payment.failed` event, please additionally check `data.object.status`:

* When `status = "failed"`, handle according to existing payment failure logic
* When `status = "frozen"`, we recommend marking the order as "Risk Control Frozen" or "Pending Risk Review"

For `frozen` scenarios, **do not** handle as a normal payment failure, and we do not recommend automatically performing the following actions:

* Automatic order replenishment
* Automatic delivery
* Automatic release of service entitlements or access permissions

## Event Examples

### payment.completed

```json theme={"system"}
{
  "id": "evt_1778835561972546443",
  "type": "payment.completed",
  "created_at": 1778835561,
  "data": {
    "object": {
      "amount": 1,
      "status": "completed",
      "currency": "USDT",
      "order_id": "order_56929d9f",
      "session_id": "20260515110100101170210000082012",
      "exchange_rate": "",
      "source": "api"
    }
  }
}
```

### payment.failed

```json theme={"system"}
{
  "id": "evt_1778834854265555041",
  "type": "payment.failed",
  "created_at": 1778834851,
  "data": {
    "object": {
      "amount": 1,
      "status": "frozen",
      "currency": "USDT",
      "order_id": "order_31509b43",
      "session_id": "20260515110100101170210000082010",
      "exchange_rate": "",
      "source": "api"
    }
  }
}
```

### payment.expired

```json theme={"system"}
{
  "id": "evt_1778836650899324281",
  "type": "payment.expired",
  "created_at": 1778836650,
  "data": {
    "object": {
      "amount": 0,
      "status": "expired",
      "currency": "",
      "order_id": "order_d3ff11a8",
      "session_id": "20260515110100101170210000082011",
      "exchange_rate": "",
      "source": "api"
    }
  }
}
```

### payment.cancelled

```json theme={"system"}
{
  "id": "evt_1770864227443530013",
  "type": "payment.cancelled",
  "created_at": 1770864227,
  "data": {
    "object": {
      "amount": 0,
      "status": "canceled",
      "currency": "",
      "order_id": "order_29",
      "session_id": "20260212110100101170210000028002",
      "exchange_rate": "",
      "source": "api"
    }
  }
}
```

## Related Pages

* [Pre-integration Preparation](/zh/api/getting-started)
