Stash Webhooks

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 TypeRequiredProductsDescription
PURCHASE_SUCCEEDEDYesPay, WebshopPurchase successfully completed. Use to grant items to the player. source is "StashPay", "Cart" or "BuyNow".
PURCHASE_FAILEDNoPay, WebshopA payment was refused or canceled. Use it for support and analytics, not to grant or revoke items. reason carries the refusal reason: never show it to the player.
PURCHASE_REFUNDEDRecommendedPay, WebshopFull or partial refund processed. Use to revoke or adjust granted items.
OPEN_PAYMENTNoPay, WebshopPayment process initiated (user starts checkout).
FREE_ITEM_REDEEMEDNoPay, WebshopA player claimed a free item.
LOGGED_INNoWebshopA player signed in to your webshop with a login code, an authenticated URL or your own OAuth 2.0 server.
MUTATE_CARTNoWebshopCart contents changed (add/remove/quantity).
VIEW_ITEMNoWebshopUser viewed an item. At most once per 100 seconds per item per player; a view without a userId is sent once per view, up to a per-shop limit.
VIEW_CATALOG_SECTIONNoWebshopUser scrolled a catalog section into view. At most once per 100 seconds per section per player; a view without a userId is sent once per view, up to a per-shop limit.
VIEW_CHECKOUT_PAGENoWebshopUser opened the checkout page.
VIEW_PRODUCT_DETAIL_PAGENoWebshopUser opened a product detail page.
CART_BUTTON_CLICKNoWebshopUser interacted with a cart button.
BUY_NOW_ITEM_CLICKNoWebshopUser clicked an item that opens checkout directly, without the cart.
PAY_BUTTON_CLICKNoPay, WebshopUser clicked the pay button on the checkout page.
LOYALTY_MILESTONE_CLAIMEDNoWebshopPlayer claimed a loyalty milestone. Only fires when the loyalty program uses async webhook delivery mode.
LOGIN_FAILEDNoWebshopA player's sign-in to your webshop was declined, by your server or by Stash. reason says why. Off until Stash turns it on for your shop.
OFFER_SLOT_MISMATCHNoWebshopOffers used a template key, or reward, buy button or text slot names, their catalog's templates do not have. The offers still showed. At most once per clock hour per template and set of mismatches.
CATALOG_REJECTEDNoWebshopStash could not fetch your Real-time Catalog or decode its response. At most once per shop per 10-minute window.
CATALOG_ITEMS_DROPPEDNoWebshopYour Real-time Catalog was shown, but items were left out or a declared bonus has a missing or invalid picture. At most once per shop per 10-minute window from Stash's server, and once from your webshop.
CATALOG_DATA_CONFLICTNoWebshopYour Real-time Catalog contradicts itself or your shop's configuration. At most once per shop and conflict per 10-minute window.
CATALOG_TEXT_FALLBACKNoWebshopCatalog texts showed their defaultText because their localizationKey is not in your shop's localizations, or has no translation in the player's language. The offers still showed. At most once per shop per clock hour.

CATALOG_REJECTED, CATALOG_ITEMS_DROPPED, CATALOG_DATA_CONFLICT and CATALOG_TEXT_FALLBACK report problems in your Real-time Catalog that you can fix. To start receiving them, contact your Stash engineering partner.

LOGIN_FAILED is off by default, so an endpoint that rejects unknown event types is not affected. Once your endpoint accepts the event, contact your Stash engineering partner to turn it on for your shop.

Payload structure

All v1 webhooks follow a consistent base structure:

Base Webhook Structure
{
  "type": "PURCHASE_SUCCEEDED",
  "environment": "test",
  "shopId": "your-shop-uuid",
  "purchaseSucceeded": {
    // Event-specific payload
  }
}

type, environment and shopId are on every v1 event. The event's payload sits under a key named for the event, such as purchaseSucceeded for PURCHASE_SUCCEEDED; each example below shows its key.

  • environment is "test" (test/staging) or "production" (live). Use it to distinguish test traffic from real transactions.
  • shopId is the ID of the shop that sent the event. Use it to route events when one endpoint serves several shops.

Webshop session

checkoutSessionId identifies one player's browsing session in your webshop. Events from the same session carry the same value, from catalog views through checkout and the purchase. After 30 minutes without activity, the player's next action starts a new session with a new checkoutSessionId. A new browser tab can also start a new session. Treat it as an opaque string.

  • Group a session's events: collect every event with the same checkoutSessionId.
  • Join a purchase to its funnel: a PURCHASE_SUCCEEDED or PURCHASE_FAILED carries the checkoutSessionId of the views and clicks that led to it.
  • Match the purchase to your purchase calls: use transactionId (or orderId on a "Cart" purchase), as described under PURCHASE_SUCCEEDED. checkoutSessionId is not sent on RegisterPayment or ConfirmPayment.

On PURCHASE_REFUNDED, checkoutSessionId is the session of the original purchase, not of the refund.

These events can carry it: PURCHASE_SUCCEEDED, PURCHASE_FAILED, PURCHASE_REFUNDED, FREE_ITEM_REDEEMED, LOGGED_IN, VIEW_ITEM, VIEW_CATALOG_SECTION, VIEW_CHECKOUT_PAGE, VIEW_PRODUCT_DETAIL_PAGE, CART_BUTTON_CLICK, BUY_NOW_ITEM_CLICK and PAY_BUTTON_CLICK. It is omitted when the event is not tied to a webshop session, for example a purchase through a Stash Pay checkout link your backend created. It may also be omitted when analyticsConsent is "DECLINED" or "UNANSWERED". OPEN_PAYMENT, MUTATE_CART, LOYALTY_MILESTONE_CLAIMED, LOGIN_FAILED, OFFER_SLOT_MISMATCH and the CATALOG_* events never carry it.

analyticsConsent is the player's answer to your webshop's analytics consent banner. VIEW_ITEM, VIEW_CATALOG_SECTION and BUY_NOW_ITEM_CLICK carry it.

ValueMeaning
"ACCEPTED"The player accepted analytics.
"DECLINED"The player declined analytics.
"UNANSWERED"The player has not answered the banner yet.

Depending on your webshop, userId and checkoutSessionId are either sent with every answer, or omitted unless the answer is "ACCEPTED". A view or click without a userId can also come from a signed-out visitor. To find out which applies to your shop, contact your Stash engineering partner.

The field is omitted when no answer was reported, for example when your webshop shows no consent banner. It describes the banner answer only: it is not a permission that Stash grants, and it does not authenticate the event.

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:

sourcePurchase
"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, orderId carries that ID. transactionId may be absent on a "Cart" purchase, so match on orderId there.

Each entry in items describes one purchased item. For a Real-time Catalog, the offer fields repeat what your catalog sent for that item when the purchase started, so you can attribute the purchase to the offer as the player saw it:

FieldTypeDescription
idstringThe item's product ID. Several offers can share one product ID; use guid to tell them apart.
guidstring (optional)The guid of the offer the item was sold under: the guid your catalog sent, or the one Stash generated for the item when your catalog sent none. On a "Cart" purchase it matches the guid on ConfirmPayment.
quantitynumberUnits bought.
pricestringPrice of one unit.
priceIdstring (optional)The Studio price ID the item was priced from: your catalog's price.priceId, or the item's product ID when the price was looked up by product ID. Only on "Cart" purchases, and omitted when your catalog sent a price.amount. See Studio price lookup.
metadataobject (optional)Metadata attached to the item, such as the metadata you set on a catalog item.
purchasePackIdstring (optional)The id of the purchase pack the item belongs to.
versionstring (optional)The offer's version.
templateKeystring (optional)The offer's templateKey.

Optional fields are omitted when Stash has no value for them, for example when the catalog did not send them. PURCHASE_FAILED and PURCHASE_REFUNDED items carry the same fields.

paymentMethodType says how the player paid. It is omitted when the payment method is unknown.

paymentMethodTypePayment method
"PAYMENT_METHOD_TYPE_CARD"Credit or debit card
"PAYMENT_METHOD_TYPE_APPLE_PAY"Apple Pay
"PAYMENT_METHOD_TYPE_GOOGLE_PAY"Google Pay
"PAYMENT_METHOD_TYPE_PAYPAL"PayPal
"PAYMENT_METHOD_TYPE_KLARNA"Klarna
"PAYMENT_METHOD_TYPE_IDEAL"iDEAL
"PAYMENT_METHOD_TYPE_EPS"EPS
"PAYMENT_METHOD_TYPE_AFTERPAY"Afterpay
"PAYMENT_METHOD_TYPE_BANCONTACT"Bancontact
Purchase Succeeded Event (Stash Pay example)
{
  "type": "PURCHASE_SUCCEEDED",
  "environment": "test",
  "shopId": "your-shop-uuid",
  "purchaseSucceeded": {
    "timeMillis": 1640995200000,
    "transactionId": "txn_789",
    "orderId": "order_abc123",
    "checkoutLinkId": "checkout_xyz789",
    "currency": "USD",
    "userId": "user_123",
    "items": [
      {
        "id": "item_456",
        "guid": "item_456_summer",
        "quantity": 2,
        "price": "9.99",
        "metadata": {
          "category": "weapons",
          "rarity": "legendary"
        },
        "purchasePackId": "summer-offer",
        "version": "5",
        "templateKey": "starter-offer-v2"
      }
    ],
    "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",
    "paymentMethodType": "PAYMENT_METHOD_TYPE_CARD",
    "checkoutSessionId": "8f3c2b1e-6a4d-4c9e-9b2f-5d7a1e0c3b46"
  }
}

Common fields

Several fields appear across multiple webhook types. Only type, environment and shopId are on every v1 event; check the event's example above for the rest.

FieldTypeDescription
typestringEvent type identifier (e.g. "PURCHASE_SUCCEEDED").
environmentstringEnvironment that sent the webhook. Always present. "test" or "production".
shopIdstringID of the shop that sent the event. Always present on v1 events.
userIdstringExternal user identifier from your system.
regionCodestringUnicode CLDR region code (e.g. "US", "FR") based on user location.
ipAddressstringUser's IP address when the event occurred.
timeMillisnumberWhen the event happened, in milliseconds since the Unix epoch. Present on PURCHASE_SUCCEEDED, PURCHASE_REFUNDED, FREE_ITEM_REDEEMED, MUTATE_CART, LOYALTY_MILESTONE_CLAIMED, CATALOG_REJECTED, CATALOG_ITEMS_DROPPED, CATALOG_DATA_CONFLICT and CATALOG_TEXT_FALLBACK. Not on the view and click events.
checkoutSessionIdstringThe player's webshop browsing session: the same value on every event of the session, and a new value after 30 minutes without activity. Omitted when the event is not tied to a webshop session, and may be omitted when analyticsConsent is "DECLINED" or "UNANSWERED". See Webshop session.
analyticsConsentstringThe player's answer to your webshop's analytics consent banner: "ACCEPTED", "DECLINED" or "UNANSWERED". On VIEW_ITEM, VIEW_CATALOG_SECTION and BUY_NOW_ITEM_CLICK; omitted when no answer was reported. See Analytics consent.

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 TypeDescriptionTrigger
subscription.createdNew subscription createdPlayer completes subscription checkout
subscription.updatedSubscription changedPlan or status was modified
subscription.canceledSubscription canceledPlayer cancels their subscription
subscription.reactivatedSubscription reactivatedCanceled subscription was reactivated before expiration
subscription.expiredSubscription expiredSubscription reached terminal state
subscription.payment_failedPayment failedRenewal payment attempt failed; subscription enters past_due
subscription.payment_succeededPayment succeededRenewal payment processed successfully

v2 Payload structure

Subscription webhooks use a different structure from v1 events:

v2 Webhook Structure
{
  "type": "subscription.event_name",
  "data": {
    // Subscription object
  }
}

Detailed subscription payloads

Triggered when a new subscription is created.

subscription.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

FieldTypeDescription
idstringUnique identifier for the subscription.
external_account_idstringIdentifier for the player/user.
plan_idstringPlan identifier.
statusstringactive, past_due, canceled, or expired.
periodobjectBilling period with value (integer) and unit (day / week / month / year).
trial_endstring (nullable)ISO 8601 timestamp when trial ends.
access_end_datestringISO 8601 timestamp when access expires.
current_period_endstringISO 8601 timestamp for current period end.
next_billing_datestringISO 8601 timestamp for next billing attempt.
cancel_at_period_endbooleanWhether cancellation is scheduled.
canceled_atstring (nullable)ISO 8601 timestamp when canceled.
created_atstringISO 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 TypeDescriptionTrigger
payment.succeededPayment completed successfullyA payment is captured
payment.failedPayment attempt failedA payment is declined or fails
payment.refundedPayment was refundedA refund is processed
dispute.openedDispute openedA cardholder disputes a payment
dispute.closedDispute closedA dispute is resolved (includes won boolean)

Detailed payment payloads

Triggered when a payment completes successfully.

payment.succeeded
{
  "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

FieldTypeDescription
idstringPayment identifier.
external_account_idstringIdentifier for the player/user.
currencystringCurrency code (e.g. "USD", "EUR").
subscription_idstring (optional)Subscription identifier when the payment is related to a subscription.
succeeded_atstringISO 8601 timestamp when payment succeeded (payment.succeeded only).
failed_atstringISO 8601 timestamp when payment failed (payment.failed only).
reasonstringNormalized failure reason (payment.failed only).
refunded_atstringISO 8601 timestamp when payment was refunded (payment.refunded only).
taxstring (optional)Tax amount as decimal string (payment.succeeded and payment.failed).
totalstringTotal payment amount as decimal string (payment.succeeded and payment.failed).
total_refundedstringTotal refunded amount as decimal string (payment.refunded only).
total_disputedstringTotal disputed amount as decimal string (dispute.opened and dispute.closed).
wonbooleanWhether you won the dispute (dispute.closed only).
metadataobject (optional)Custom metadata attached to the payment.

How is this guide?

On this page