Skip to main content

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.

Basic Information

ItemValue
Production Base URLhttps://api.stablepay.co
API Prefix/api/v1
ProtocolHTTPS
Data FormatJSON
Character EncodingUTF-8

Prerequisites

Before you begin integration, please ensure you have:
  1. Registered a StablePay merchant account and completed the qualification review
  2. Created a store in the merchant dashboard, selected API as the channel type, and waited for approval
  3. 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

Required Headers

All API requests must include the following headers:
HeaderRequiredDescription
AuthorizationYesFormat: Bearer {api_key}
Content-TypeRequired for POST/PUTFixed as application/json. Not required for GET requests
X-StablePay-TimestampYesUnix timestamp (seconds). Must not differ from server time by more than 5 minutes
X-StablePay-NonceYesRandom string (UUID v4 recommended). Used for replay attack prevention; must be unique for each request
X-StablePay-SignatureYesLowercase 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.

Required Headers by Endpoint

CategoryExample PathAuthorizationSigning Trio
PaymentsPOST /api/v1/checkout/sessions/create
PaymentsGET /api/v1/checkout/sessions/{session_id}✅ (empty body)
PaymentsPOST /api/v1/checkout/sessions/{session_id}/cancel
RefundsPOST /api/v1/refunds/create
RefundsGET /api/v1/refunds/{refund_id}✅ (empty body)
RefundsPOST /api/v1/refunds/{refund_id}/cancel
SubscriptionsPOST /api/v1/subscriptions/create
SubscriptionsGET /api/v1/subscriptions/{subscription_id}✅ (empty body)
SubscriptionsGET /api/v1/subscriptions✅ (empty body)
SubscriptionsPOST /api/v1/subscriptions/{subscription_id}/cancel
InvoicesGET /api/v1/invoices/{invoice_id}✅ (empty body)
InvoicesGET /api/v1/invoices✅ (empty body)
Payment LookupGET /api/v1/payment/{payment_id}✅ (empty body)
Payment MethodsGET /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:
EndpointIdempotency Key
Create RefundRequest body refund_id
Create SubscriptionRequest 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

CategoryEvent Types
Paymentspayment.completed, payment.failed, payment.expired, payment.cancelled
Refundsrefund.succeeded, refund.failed
Subscriptionssubscription.created, subscription.trialing, subscription.active, subscription.past_due, subscription.canceled, subscription.updated, subscription.incomplete_expired
Invoicesinvoice.created, invoice.paid, invoice.payment_failed

Webhook Headers

HeaderDescription
Content-TypeFixed as application/json
User-AgentStablePay-Webhook/1.0
X-StablePay-SignatureLowercase hex signature of {timestamp}.{raw_body} using HMAC-SHA256
X-StablePay-TimestampUnix timestamp (seconds)
X-StablePay-NonceRandom string for additional replay protection or logging; not included in signature calculation
X-StablePay-Event-TypeEvent type, e.g., payment.completed
X-StablePay-Event-IDUnique event ID for idempotent deduplication

Signature Verification Steps

  1. Extract X-StablePay-Signature and X-StablePay-Timestamp from headers; optionally log X-StablePay-Nonce for additional replay protection or logging
  2. Verify the timestamp is within 5 minutes of current time (replay protection)
  3. Take the raw request body bytes (do not parse then re-serialize) and concatenate as follows:
    sign_payload = {timestamp} + "." + {raw_body}
    
  4. Apply HMAC-SHA256 using your Secret Key to get a lowercase hex string
  5. 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:
HTTPcodeMeaning
400parameter_missing / invalid_request_errorMissing parameter or invalid format
401invalid_api_keyInvalid API Key or signature verification failed
403Insufficient permissions to access resource
404resource_not_foundResource does not exist
409conflictIdempotency conflict (same key with different parameters)
429DAILY_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
Last modified on April 30, 2026