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 each webhook type 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" or "Cart".
PURCHASE_REFUNDEDRecommendedPay, WebshopFull or partial refund processed. Use to revoke or adjust granted items.
CREATE_PAYMENT_INTENTNoPay, WebshopPayment process initiated (user starts checkout).
FREE_ITEM_REDEEMEDNoPay, WebshopA free item was claimed (promotional rewards, etc.).
MUTATE_CARTNoWebshopCart contents changed (add/remove/quantity).
VIEW_ITEMNoWebshopUser viewed an item (debounced once per 100s per item).
VIEW_CHECKOUT_PAGENoWebshopUser opened the checkout page.
VIEW_PRODUCT_DETAIL_PAGENoWebshopUser opened a product detail page.
CART_BUTTON_CLICKNoWebshopUser interacted with a cart button.

Payload structure

All v1 webhooks follow a consistent base structure:

Base Webhook 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 is "StashPay" for Stash Pay or "Cart" for Webshop.

Purchase Succeeded Event (Stash Pay example)
{
  "type": "PURCHASE_SUCCEEDED",
  "environment": "test",
  "purchaseSucceeded": {
    "timeMillis": 1640995200000,
    "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"
  }
}

Triggered when a refund is successfully processed for a previous purchase. Both full and partial refunds fire this event — multiple partial refunds on the same order each generate a separate event.

The items array contains all items from the original purchase, not just refunded ones. The total field reflects the actual refund amount.

Purchase Refunded Event
{
  "type": "PURCHASE_REFUNDED",
  "environment": "test",
  "purchaseRefunded": {
    "timeMillis": 1640995200000,
    "orderId": "order_abc123",
    "userId": "user_123",
    "items": [
      {
        "id": "item_456",
        "quantity": 2,
        "price": "9.99",
        "metadata": {
          "category": "weapons",
          "rarity": "legendary"
        }
      }
    ],
    "total": "21.48",
    "currency": "USD",
    "reason": "Customer requested refund",
    "regionCode": "US",
    "source": "StashPay"
  }
}

Triggered when the payment process is initiated. The source field is "StashPay" for Stash Pay or "Cart" for Webshop.

Create Payment Intent Event
{
  "type": "CREATE_PAYMENT_INTENT",
  "environment": "test",
  "openPayment": {
    "cartId": "cart_789",
    "userId": "user_123",
    "items": [
      {
        "id": "item_456",
        "quantity": 2
      }
    ],
    "source": "StashPay"
  }
}

Triggered when a free item is redeemed (promotional rewards, claim-codes, etc.).

Free Item Redeemed Event
{
  "type": "FREE_ITEM_REDEEMED",
  "environment": "test",
  "freeItemRedeemed": {
    "userId": "user_123",
    "itemId": "item_free_001",
    "ipAddress": "192.168.1.1",
    "metadata": {
      "promotionId": "summer_promo",
      "redeemCode": "SUMMER2024"
    }
  }
}

Triggered when cart contents are modified. Quantity deltas can be negative (removal) or positive (addition).

Mutate Cart Event
{
  "type": "MUTATE_CART",
  "environment": "test",
  "mutateCart": {
    "cartId": "cart_789",
    "timeMillis": 1640995200000,
    "userId": "user_123",
    "items": [
      {
        "id": "item_456",
        "quantity": 3
      },
      {
        "id": "item_789",
        "quantity": -1
      }
    ],
    "regionCode": "US",
    "source": "Cart",
    "ipAddress": "192.168.1.1"
  }
}

Triggered when a user views an item. Debounced to once per 100 seconds per item per user.

View Item Event
{
  "type": "VIEW_ITEM",
  "environment": "test",
  "viewItem": {
    "userId": "user_123",
    "itemId": "item_456",
    "regionCode": "US"
  }
}

Triggered when the checkout page is viewed.

View Checkout Page Event
{
  "type": "VIEW_CHECKOUT_PAGE",
  "environment": "test",
  "viewCheckoutPage": {
    "userId": "user_123",
    "regionCode": "US"
  }
}

Triggered when a product detail page is viewed.

View Product Detail Page Event
{
  "type": "VIEW_PRODUCT_DETAIL_PAGE",
  "environment": "test",
  "viewProductDetailsPage": {
    "userId": "user_123",
    "itemId": "item_456",
    "regionCode": "US"
  }
}

Triggered when a cart-related button (icon, badge, etc.) is clicked.

Cart Button Click Event
{
  "type": "CART_BUTTON_CLICK",
  "environment": "test",
  "sendCartButtonClick": {
    "userId": "user_123",
    "regionCode": "US"
  }
}

Common fields

Several fields appear across multiple webhook types:

FieldTypeDescription
typestringEvent type identifier (e.g. "PURCHASE_SUCCEEDED").
environmentstringEnvironment that sent the webhook. Always present. "test" or "production".
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.
timeMillisnumberTimestamp in milliseconds since Unix epoch.

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"
  }
}

Triggered when a subscription's plan or status changes.

subscription.updated
{
  "type": "subscription.updated",
  "data": {
    "id": "sub_xyz789",
    "external_account_id": "player_123",
    "plan_id": "plan_abc123",
    "status": "active",
    "period": {
      "value": 1,
      "unit": "month"
    },
    "trial_end": null,
    "access_end_date": "2024-04-01T00:00:00Z",
    "current_period_end": "2024-04-01T00:00:00Z",
    "next_billing_date": "2024-04-01T00:00:00Z",
    "cancel_at_period_end": false,
    "canceled_at": null,
    "created_at": "2024-01-01T00:00:00Z"
  }
}

Triggered when a player cancels their subscription.

subscription.canceled
{
  "type": "subscription.canceled",
  "data": {
    "id": "sub_xyz789",
    "external_account_id": "player_123",
    "plan_id": "plan_abc123",
    "status": "canceled",
    "period": {
      "value": 1,
      "unit": "month"
    },
    "trial_end": null,
    "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": true,
    "canceled_at": "2024-02-15T14:30:00Z",
    "created_at": "2024-01-01T00:00:00Z"
  }
}

Triggered when a canceled subscription is reactivated before expiration.

subscription.reactivated
{
  "type": "subscription.reactivated",
  "data": {
    "id": "sub_xyz789",
    "external_account_id": "player_123",
    "plan_id": "plan_abc123",
    "status": "active",
    "period": {
      "value": 1,
      "unit": "month"
    },
    "trial_end": null,
    "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"
  }
}

Triggered when a subscription reaches its terminal state.

subscription.expired
{
  "type": "subscription.expired",
  "data": {
    "id": "sub_xyz789",
    "external_account_id": "player_123",
    "plan_id": "plan_abc123",
    "status": "expired",
    "period": {
      "value": 1,
      "unit": "month"
    },
    "trial_end": null,
    "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": true,
    "canceled_at": "2024-02-15T14:30:00Z",
    "created_at": "2024-01-01T00:00:00Z"
  }
}

Triggered when a renewal payment attempt fails. The subscription enters past_due status and Stash will retry.

subscription.payment_failed
{
  "type": "subscription.payment_failed",
  "data": {
    "id": "sub_xyz789",
    "external_account_id": "player_123",
    "plan_id": "plan_abc123",
    "status": "past_due",
    "period": {
      "value": 1,
      "unit": "month"
    },
    "trial_end": null,
    "access_end_date": "2024-03-03T00:00:00Z",
    "current_period_end": "2024-03-01T00:00:00Z",
    "next_billing_date": "2024-03-02T00:00:00Z",
    "cancel_at_period_end": false,
    "canceled_at": null,
    "created_at": "2024-01-01T00:00:00Z"
  }
}

Triggered when a renewal payment is processed successfully.

subscription.payment_succeeded
{
  "type": "subscription.payment_succeeded",
  "data": {
    "id": "sub_xyz789",
    "external_account_id": "player_123",
    "plan_id": "plan_abc123",
    "status": "active",
    "period": {
      "value": 1,
      "unit": "month"
    },
    "trial_end": null,
    "access_end_date": "2024-04-01T00:00:00Z",
    "current_period_end": "2024-04-01T00:00:00Z",
    "next_billing_date": "2024-04-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.

How is this guide?