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.
This document outlines the prerequisites for integrating with the StablePay API, including account and key acquisition, request signature algorithms, Webhook event notification mechanisms, and the required headers for each endpoint.
| Item | Value |
|---|
| Production Base URL | https://api.stablepay.co |
| API Prefix | /api/v1 |
| Protocol | HTTPS |
| Data Format | JSON |
| Character Encoding | UTF-8 |
Prerequisites
Before you begin integration, please ensure you have:
- Registered a StablePay merchant account and completed the qualification review
- Created a store in the merchant dashboard, selected API as the channel type, and waited for approval
- Generated and obtained the following from the “Key Management” menu in your store:
- API Key: Used in the
Authorization header to identify the caller
- Secret Key: Used to generate request signatures. Store this securely and never commit it to public repositories or share it with others
Authentication & Signing
All API requests must include the following headers:
| Header | Required | Description |
|---|
Authorization | Yes | Format: Bearer {api_key} |
Content-Type | Required for POST/PUT | Fixed as application/json. Not required for GET requests |
X-StablePay-Timestamp | Yes | Unix timestamp (seconds). Must not differ from server time by more than 5 minutes |
X-StablePay-Nonce | Yes | Random string (UUID v4 recommended). Used for replay attack prevention; must be unique for each request |
X-StablePay-Signature | Yes | Lowercase hexadecimal string generated by applying HMAC-SHA256 to the signature payload using your Secret Key |
Signature Payload Construction
sign_payload = {timestamp} + "." + {nonce} + "." + {requestBody}
{requestBody}: For POST / PUT requests, use the raw JSON string (do not re-serialize or format)
- For GET / DELETE methods without a request body: Use an empty string for
{requestBody}
- Signing algorithm:
HMAC-SHA256(sign_payload, secret_key) → lowercase hex string
Code Examples
TIMESTAMP=$(date +%s)
NONCE=$(uuidgen)
BODY='{"amount":1999,"currency":"USD"}'
SIGN_PAYLOAD="${TIMESTAMP}.${NONCE}.${BODY}"
SIGNATURE=$(echo -n "${SIGN_PAYLOAD}" | openssl dgst -sha256 -hmac "${SECRET_KEY}" | awk '{print $2}')
curl -X POST https://api.stablepay.co/api/v1/checkout/sessions/create \
-H "Authorization: Bearer ${API_KEY}" \
-H "X-StablePay-Timestamp: ${TIMESTAMP}" \
-H "X-StablePay-Nonce: ${NONCE}" \
-H "X-StablePay-Signature: ${SIGNATURE}" \
-H "Content-Type: application/json" \
-d "${BODY}"
The requestBody in the signature payload must be the exact raw bytes being sent. Do not format or reorder the JSON fields after calculating the signature, or the verification will fail.
| Category | Example Path | Authorization | Signing Trio |
|---|
| Payments | POST /api/v1/checkout/sessions/create | ✅ | ✅ |
| Payments | GET /api/v1/checkout/sessions/{session_id} | ✅ | ✅ (empty body) |
| Payments | POST /api/v1/checkout/sessions/{session_id}/cancel | ✅ | ✅ |
| Refunds | POST /api/v1/refunds/create | ✅ | ✅ |
| Refunds | GET /api/v1/refunds/{refund_id} | ✅ | ✅ (empty body) |
| Refunds | POST /api/v1/refunds/{refund_id}/cancel | ✅ | ✅ |
| Subscriptions | POST /api/v1/subscriptions/create | ✅ | ✅ |
| Subscriptions | GET /api/v1/subscriptions/{subscription_id} | ✅ | ✅ (empty body) |
| Subscriptions | GET /api/v1/subscriptions | ✅ | ✅ (empty body) |
| Subscriptions | POST /api/v1/subscriptions/{subscription_id}/cancel | ✅ | ✅ |
| Invoices | GET /api/v1/invoices/{invoice_id} | ✅ | ✅ (empty body) |
| Invoices | GET /api/v1/invoices | ✅ | ✅ (empty body) |
| Payment Lookup | GET /api/v1/payment/{payment_id} | ✅ | ✅ (empty body) |
| Payment Methods | GET /api/v1/payment_method/{payment_method_id} | ✅ | ✅ (empty body) |
POST /api/v1/subscriptions/create additionally requires the Idempotency-Key header. When retrying with the same key, the server will return the originally created subscription object.
Idempotency
The following endpoints support idempotency using merchant-defined IDs as keys:
| Endpoint | Idempotency Key |
|---|
| Create Refund | Request body refund_id |
| Create Subscription | Request header Idempotency-Key |
When submitting duplicate requests with the same idempotency key, the server returns the originally created resource without creating duplicate data.
Webhook Notifications
StablePay pushes critical events asynchronously to your configured callback URL via Webhooks.
Event Types
| Category | Event Types |
|---|
| Payments | payment.completed, payment.failed, payment.expired, payment.cancelled |
| Refunds | refund.succeeded, refund.failed |
| Subscriptions | subscription.created, subscription.trialing, subscription.active, subscription.past_due, subscription.canceled, subscription.updated, subscription.incomplete_expired |
| Invoices | invoice.created, invoice.paid, invoice.payment_failed |
| Header | Description |
|---|
Content-Type | Fixed as application/json |
User-Agent | StablePay-Webhook/1.0 |
X-StablePay-Signature | Lowercase hex signature of {timestamp}.{raw_body} using HMAC-SHA256 |
X-StablePay-Timestamp | Unix timestamp (seconds) |
X-StablePay-Nonce | Random string for additional replay protection or logging; not included in signature calculation |
X-StablePay-Event-Type | Event type, e.g., payment.completed |
X-StablePay-Event-ID | Unique event ID for idempotent deduplication |
Signature Verification Steps
-
Extract
X-StablePay-Signature and X-StablePay-Timestamp from headers; optionally log X-StablePay-Nonce for additional replay protection or logging
-
Verify the timestamp is within 5 minutes of current time (replay protection)
-
Take the raw request body bytes (do not parse then re-serialize) and concatenate as follows:
sign_payload = {timestamp} + "." + {raw_body}
-
Apply HMAC-SHA256 using your Secret Key to get a lowercase hex string
-
Compare with the signature in the header using constant-time comparison (e.g.,
hmac.compare_digest)
Verification Example (Python)
import hmac, hashlib, time
def verify_webhook(headers, raw_body: bytes, secret_key: str) -> bool:
ts = headers["X-StablePay-Timestamp"]
sig = headers["X-StablePay-Signature"]
if abs(int(time.time()) - int(ts)) > 300:
return False
sign_payload = f"{ts}.".encode() + raw_body
expected = hmac.new(
secret_key.encode(), sign_payload, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, sig)
Response Requirements & Retry Policy
- Your server must return HTTP
2xx within 30 seconds
429 or 5xx responses enter the retry queue; other 4xx responses are not retried
- Maximum 10 retries with exponential backoff intervals (minutes):
2, 4, 8, 16, 32, 64, 128, 256, 512, 1024
Idempotent Consumption
Use X-StablePay-Event-ID (or the id field in the request body) as your idempotency key. Check whether the event has been processed before handling to avoid side effects from duplicate deliveries.
Error Responses
Errors are returned using HTTP status codes with a JSON response body. The error field may be a string or structured object:
{
"error": {
"type": "invalid_request_error",
"code": "parameter_missing",
"message": "Missing required parameter: refund_id",
"param": "refund_id",
"doc_url": "https://docs.stablepayfi.ai/errors#parameter_missing"
},
"request_id": "req_xxx",
"timestamp": 1774924800
}
Common error codes:
| HTTP | code | Meaning |
|---|
| 400 | parameter_missing / invalid_request_error | Missing parameter or invalid format |
| 401 | invalid_api_key | Invalid API Key or signature verification failed |
| 403 | — | Insufficient permissions to access resource |
| 404 | resource_not_found | Resource does not exist |
| 409 | conflict | Idempotency conflict (same key with different parameters) |
| 429 | DAILY_REFUND_LIMIT_EXCEEDED etc. | Rate limit or quota exceeded |
Security Best Practices
- Secret Keys should only be used server-side and must never appear in frontend code, APKs, mini-program packages, or logs
- We recommend injecting Secret Keys via a Key Management Service (KMS/Vault/SSM) in your CI/CD pipeline
- Webhook callback URLs must use HTTPS
- Perform signature verification + idempotent deduplication + business state validation for every received Webhook (e.g., ensure an order is “paid” before processing a refund)
- Regularly rotate your API Keys and Secret Keys in the merchant dashboard