Skip to main content
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 page.

Step 1: Create an API Store

  1. Open the Stores and Developers 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

Step 2: Configure Webhook after Store Activation

Once the store status changes to Enabled:
  1. Return to the Stores and Developers 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
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.

Event Types

CategoryEvent Type
Paymentpayment.completed, payment.failed, payment.expired, payment.cancelled
Refundrefund.succeeded, refund.failed
Subscriptionsubscription.created, subscription.trialing, subscription.active, subscription.past_due, subscription.canceled, subscription.updated, subscription.incomplete_expired
Invoiceinvoice.created, invoice.paid, invoice.payment_failed

Webhook Request Headers

HeaderDescription
Content-TypeFixed application/json
User-AgentStablePay-Webhook/1.0
X-StablePay-SignatureLowercase hex signature of {timestamp}.{nonce}.{raw_body} using HMAC-SHA256
X-StablePay-TimestampUnix timestamp (seconds)
X-StablePay-NonceRandom string (16-64 characters), included in signature calculation, unique per notification
X-StablePay-Event-TypeEvent type, e.g., payment.completed
X-StablePay-Event-IDUnique 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:
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.

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:
"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

{
  "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

{
  "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

{
  "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

{
  "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

{
  "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"
    }
  }
}
Last modified on June 29, 2026