Webhook List
Comprehensive list of all webhook events that Stash can send to your backend, including event types, triggers, payload structures, and detailed examples for purchase, subscription, and payment webhooks across Stash products.
This page lists all webhook events Stash can send to your backend. Each event is triggered by a specific user action or system event. Use the table below to see what's available, then expand the tabs for full payload examples.
Product-specific events: Some events are specific to Stash Pay or Stash Webshop. See the Stash Pay integration guide or Stash Webshop integration guide for product-specific webhook details.
Available webhooks
PURCHASE_SUCCEEDED is the primary event for granting items; always subscribe to it. PURCHASE_REFUNDED should be handled to revoke or adjust granted items. The remaining events are optional and used mainly for analytics, marketing, and behavior tracking.
| Event Type | Required | Products | Description |
|---|---|---|---|
PURCHASE_SUCCEEDED | Yes | Pay, Webshop | Purchase successfully completed. Use to grant items to the player. source is "StashPay", "Cart" or "BuyNow". |
PURCHASE_FAILED | No | Pay, Webshop | A payment was refused or canceled. reason carries the refusal reason: never show it to the player. |
PURCHASE_REFUNDED | Recommended | Pay, Webshop | Full or partial refund processed. Use to revoke or adjust granted items. |
CREATE_PAYMENT_INTENT | No | Pay, Webshop | Payment process initiated (user starts checkout). |
FREE_ITEM_REDEEMED | No | Pay, Webshop | A free item was claimed (promotional rewards, etc.). |
MUTATE_CART | No | Webshop | Cart contents changed (add/remove/quantity). |
VIEW_ITEM | No | Webshop | User viewed an item (debounced once per 100s per item). |
VIEW_CHECKOUT_PAGE | No | Webshop | User opened the checkout page. |
VIEW_PRODUCT_DETAIL_PAGE | No | Webshop | User opened a product detail page. |
CART_BUTTON_CLICK | No | Webshop | User interacted with a cart button. |
LOYALTY_MILESTONE_CLAIMED | No | Webshop | Player claimed a loyalty milestone. Only fires when the loyalty program uses async webhook delivery mode. |
Payload structure
All v1 webhooks follow a consistent base structure:
{
"type": "EVENT_TYPE",
"environment": "test",
"eventData": {
// Event-specific payload data
}
}The environment field is always present. Possible values: "test" (test/staging) or "production" (live). Use it to distinguish test traffic from real transactions.
Detailed payload examples
Pick the event you're integrating to see its full JSON payload.
Triggered when a purchase is successfully completed. The source field says where the purchase started:
source | Purchase |
|---|---|
"StashPay" | A Stash Pay checkout link your backend created |
"Cart" | A Webshop cart checkout |
"BuyNow" | A single item bought directly from the Webshop, without the cart |
To match the event to the purchase your backend saw on RegisterPayment or ConfirmPayment, use transactionId: it is the transaction ID Stash sent on those calls. On a "Cart" purchase transactionId is absent, and orderId carries that ID instead.
{
"type": "PURCHASE_SUCCEEDED",
"environment": "test",
"purchaseSucceeded": {
"timeMillis": 1640995200000,
"transactionId": "txn_789",
"orderId": "order_abc123",
"checkoutLinkId": "checkout_xyz789",
"currency": "USD",
"userId": "user_123",
"items": [
{
"id": "item_456",
"quantity": 2,
"price": "9.99",
"metadata": {
"category": "weapons",
"rarity": "legendary"
}
}
],
"tax": "1.50",
"total": "21.48",
"taxDetails": {
"items": [
{
"label": "Sales Tax",
"isInclusive": false,
"amount": "1.50",
"percentage": "7.5",
"country": "US",
"state": "CA"
}
]
},
"emailMarketingOptIn": true,
"regionCode": "US",
"source": "StashPay",
"ipAddress": "192.168.1.1"
}
}Common fields
Several fields appear across multiple webhook types. Only type and environment are on every event; check the event's example above for the rest.
| Field | Type | Description |
|---|---|---|
type | string | Event type identifier (e.g. "PURCHASE_SUCCEEDED"). |
environment | string | Environment that sent the webhook. Always present. "test" or "production". |
userId | string | External user identifier from your system. |
regionCode | string | Unicode CLDR region code (e.g. "US", "FR") based on user location. |
ipAddress | string | User's IP address when the event occurred. |
timeMillis | number | When the event happened, in milliseconds since the Unix epoch. Present on PURCHASE_SUCCEEDED, PURCHASE_REFUNDED, FREE_ITEM_REDEEMED, MUTATE_CART and LOYALTY_MILESTONE_CLAIMED. Not on the view and click events. |
Subscription Events (v2)
Subscription webhooks use a v2 payload format with lowercase dot-notation event types, unlike the v1 format used by purchase and webshop events above.
Subscription events notify your system of subscription lifecycle changes. See the Subscriptions guide for full integration details.
Available subscription events
| Event Type | Description | Trigger |
|---|---|---|
subscription.created | New subscription created | Player completes subscription checkout |
subscription.updated | Subscription changed | Plan or status was modified |
subscription.canceled | Subscription canceled | Player cancels their subscription |
subscription.reactivated | Subscription reactivated | Canceled subscription was reactivated before expiration |
subscription.expired | Subscription expired | Subscription reached terminal state |
subscription.payment_failed | Payment failed | Renewal payment attempt failed; subscription enters past_due |
subscription.payment_succeeded | Payment succeeded | Renewal payment processed successfully |
v2 Payload structure
Subscription webhooks use a different structure from v1 events:
{
"type": "subscription.event_name",
"data": {
// Subscription object
}
}Detailed subscription payloads
Triggered when a new subscription is created.
{
"type": "subscription.created",
"data": {
"id": "sub_xyz789",
"external_account_id": "player_123",
"plan_id": "plan_abc123",
"status": "active",
"period": {
"value": 1,
"unit": "month"
},
"trial_end": "2024-02-01T00:00:00Z",
"access_end_date": "2024-03-01T00:00:00Z",
"current_period_end": "2024-03-01T00:00:00Z",
"next_billing_date": "2024-03-01T00:00:00Z",
"cancel_at_period_end": false,
"canceled_at": null,
"created_at": "2024-01-01T00:00:00Z"
}
}Subscription object fields
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier for the subscription. |
external_account_id | string | Identifier for the player/user. |
plan_id | string | Plan identifier. |
status | string | active, past_due, canceled, or expired. |
period | object | Billing period with value (integer) and unit (day / week / month / year). |
trial_end | string (nullable) | ISO 8601 timestamp when trial ends. |
access_end_date | string | ISO 8601 timestamp when access expires. |
current_period_end | string | ISO 8601 timestamp for current period end. |
next_billing_date | string | ISO 8601 timestamp for next billing attempt. |
cancel_at_period_end | boolean | Whether cancellation is scheduled. |
canceled_at | string (nullable) | ISO 8601 timestamp when canceled. |
created_at | string | ISO 8601 timestamp when created. |
Payment Events (v2)
Payment webhooks notify your backend about individual payment outcomes. They use the same v2 payload format as subscription events (type + data). These events fire for subscription payments and other Stash Pay checkout flows.
Available payment events
| Event Type | Description | Trigger |
|---|---|---|
payment.succeeded | Payment completed successfully | A payment is captured |
payment.failed | Payment attempt failed | A payment is declined or fails |
payment.refunded | Payment was refunded | A refund is processed |
dispute.opened | Dispute opened | A cardholder disputes a payment |
dispute.closed | Dispute closed | A dispute is resolved (includes won boolean) |
Detailed payment payloads
Triggered when a payment completes successfully.
{
"type": "payment.succeeded",
"data": {
"id": "pay_abc123",
"external_account_id": "player_123",
"currency": "USD",
"subscription_id": "sub_xyz789",
"succeeded_at": "2024-01-01T00:00:00Z",
"tax": "1.50",
"total": "10.99",
"metadata": {
"custom-key": "customer-value"
}
}
}Payment object fields
| Field | Type | Description |
|---|---|---|
id | string | Payment identifier. |
external_account_id | string | Identifier for the player/user. |
currency | string | Currency code (e.g. "USD", "EUR"). |
subscription_id | string (optional) | Subscription identifier when the payment is related to a subscription. |
succeeded_at | string | ISO 8601 timestamp when payment succeeded (payment.succeeded only). |
failed_at | string | ISO 8601 timestamp when payment failed (payment.failed only). |
reason | string | Normalized failure reason (payment.failed only). |
refunded_at | string | ISO 8601 timestamp when payment was refunded (payment.refunded only). |
tax | string (optional) | Tax amount as decimal string (payment.succeeded and payment.failed). |
total | string | Total payment amount as decimal string (payment.succeeded and payment.failed). |
total_refunded | string | Total refunded amount as decimal string (payment.refunded only). |
total_disputed | string | Total disputed amount as decimal string (dispute.opened and dispute.closed). |
won | boolean | Whether you won the dispute (dispute.closed only). |
metadata | object (optional) | Custom metadata attached to the payment. |
How is this guide?
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.
Webhook Retries and Idempotency
Learn about webhook retry behavior, how many retries are attempted, what happens on failure, and best practices for implementing idempotent webhook handlers.