Webhook Listener
Learn how to create a webhook listener on your backend to receive incoming Stash webhook requests, verify their authenticity using HMAC SHA-256 signatures, and respond appropriately to process events securely.
Begin by creating a webhook listener on your backend. A webhook listener is a server-side program that receives incoming Stash webhook requests at a designated URL, verifies their authenticity (checking the signature), and responds appropriately (if needed) to the Stash.
Example webhook
{
"type": "PURCHASE_SUCCEEDED",
"purchaseSucceeded": {
"timeMillis": 1753993257000,
"orderId": "8ZVBabLrnCMm9zPdu9QpfCWzNaA",
"currency": "usd",
"userId": "user_id",
"items": [
{
"id": "item_id",
"quantity": 1,
"price": "199"
}
],
"tax": "0",
"total": "199",
"regionCode": "US",
"source": "StashPay"
}
}Note: The source field indicates which product generated the webhook. It will be "StashPay" for Stash Pay events or "Cart" for Stash Webshop events. See the webhook event list for all available events and their product associations.
Each webhook payload includes a type field that indicates the event type enum (ex: PURCHASE_SUCCEEDED). The payload also contains a corresponding object with
event-specific details. For a complete overview of all available webhook event types and their payload structures,
refer to the webhook event list.
Signature verification
Stash signs every webhook with HMAC-SHA256. New apps (created on or after 2026-08-15) receive only the versioned header; earlier apps receive both during the migration window:
x-stash-hmac-signature— versioned, self-describing format; use this for all new integrationsstash-hmac-signature— legacy format; still sent for apps created before 2026-08-15
Versioned signature (x-stash-hmac-signature)
The versioned header carries the protocol version, your App ID, a request timestamp, and the base64 HMAC:
x-stash-hmac-signature: v1;<appId>;<unixMillisTimestamp>;<base64-hmac>| Field | Description |
|---|---|
v1 | Protocol version |
<appId> | Your immutable App ID (Studio → Project Settings → App details) |
<unixMillisTimestamp> | Request timestamp in Unix milliseconds |
<base64-hmac> | Standard base64-encoded HMAC-SHA256 signature |
The signature covers "<unixMillisTimestamp>." + <body>, where <body> is the exact raw webhook body bytes. The HMAC key is your webhook secret base64-decoded: copy the value from Studio → Project Settings → API Secrets and decode it with Buffer.from(secret, 'base64'). Reject requests where <unixMillisTimestamp> is more than 5 minutes from your server clock.
For Node.js, Python, and Go implementation code, see API Keys → HMAC Verification.
Legacy signature (stash-hmac-signature)
The legacy header contains only the base64 HMAC of the raw request body, without a timestamp or version:
stash-hmac-signature: <base64-hmac>Verification steps:
Retrieve the signature
Extract the signature from the stash-hmac-signature header of the incoming Stash webhook request.
Obtain the request body
Read the raw JSON payload of the webhook request exactly as received (do not parse and re-serialize, as this may change whitespace or formatting).
Generate your own signature
Compute HMAC-SHA256 of the raw request body, keyed by your webhook secret base64-decoded (copy from Studio → Project Settings → API Secrets). Encode the result as standard base64.
Compare signatures
Compare your generated signature to the header value using a constant-time comparison. If they match, the webhook is authentic.
Always use the exact raw request body as received. Never expose your webhook secret in client-side code or logs. Reject any request where signatures do not match.
Security Best Practices
- Always verify signatures in production: Even if optional, signature verification should be enabled for production endpoints
- Use timing-safe comparison: Use constant-time comparison functions (e.g.,
crypto.timingSafeEqual,hmac.compare_digest,hmac.Equal) to prevent timing attacks - Store secrets securely: Never commit webhook secrets to version control; use environment variables or secret management services
- Rotate secrets periodically: Periodically rotate webhook secrets and update endpoints
Disabling Signature Verification
If you choose not to verify signatures (not recommended for production), you can simply ignore the Stash-Hmac-Signature header. However, this leaves your endpoint vulnerable to fake webhook events. Always enable signature verification in production environments.
How is this guide?