Integrating Stash Pay

Learn how to integrate Stash Pay into your game or app with minimum setup. This guide covers creating checkout links, displaying checkouts in browser or in-app, and handling webhook events for secure payment processing.

Stash Pay is a direct-to-consumer payment system for games and apps.

This guide explains how to integrate Stash Pay using the basic setup: creating checkout links, showing the checkout to players, and processing purchase events.

The video below provides a quick walkthrough of the integration flow.

To create a checkout session, send a POST request from your backend to the /sdk/server/checkout_links/generate_quick_pay_url endpoint.

Include your API key in the request header as X-Stash-Api-Key: YOUR_API_KEY.

Make sure this request is made from your server, not the client, so the API key remains private.

Server SDK Integration: This guide covers the Server SDK integration method, which is the recommended approach for Stash Pay. Server SDK provides secure, server-side checkout link generation that keeps API keys private and is suitable for production use. For testing checkout links without writing code, use the Link Generator in Stash Studio.

Here's a sample payload for creating a checkout link:

{
  "item": {
    "id": "",
    "pricePerItem": "",
    "quantity": 1,
    "imageUrl": "",
    "name": "",
    "description": ""
  },
  "user": {
    "id": "",
    "validatedEmail": "",
    "profileImageUrl": "",
    "displayName": "",
    "regionCode": "",
    "platform": "UNDEFINED"
  },
  "transactionId": "",
  "regionCode": "",
  "currency": ""
}
ParameterTypeDescription
itemobjectThe item being purchased.
Fields:
- id: Unique identifier for the item.
- pricePerItem: Price per item in the smallest currency unit (e.g., cents).
- quantity: Number of items being purchased.
- imageUrl: Optional image representing the item.
- name: Name of the item.
- description: Short description of the item.
userobjectInformation about the purchasing user.
Fields:
- id: Unique user ID (from your system).
- validatedEmail: (optional) Email address if available and validated.
- profileImageUrl: (optional) Link to the user's avatar image.
- displayName: User display name.
- regionCode: (optional) User's region code for localization.
- platform: Platform string, e.g., IOS, ANDROID, or UNDEFINED.
transactionIdstringUnique transaction identifier generated by your backend for idempotency and tracking.
regionCodestring(optional) Region/country code for payment localization (e.g., "US").
currencystringISO 4217 currency code for the transaction (e.g., "USD", "EUR").

If your request succeeds, you will receive a checkout URL that you can present to the user using any of the methods described below.

Generate Checkout Response
{
  "url": "https://store.example.com/order/abc123",
  "id": "abc123",
  "regionCode": "US"
}

Displaying the checkout

You can display the checkout either in a browser or inside the game. Choose the option that best fits your integration.

Browser Purchase

To present the checkout, open the URL on the user's device using your preferred method (e.g., browser window).

Open Checkout URL in Javascript
window.open(stash_pay_url, '_blank');

Example in Unity using Application.OpenURL.

Open Checkout URL in Unity
public class CheckoutHandler : MonoBehaviour
{
    public void OpenCheckoutURL(string stash_pay_url)
    {
        Application.OpenURL(stash_pay_url);
    }
}

In-App purchase

Stash Pay can also be used as a direct replacement for traditional In-app purchases. Refer to the Unity iOS & Android Integration guide for options to present the checkout using the Stash SDK.

Processing Purchase Events

Once a player completes checkout, your system must process the event to grant items. Stash supports three patterns for handling purchase events, depending on your game's requirements.

Choosing the right pattern: See the High-Level Flow Options guide to understand when to use each pattern based on your inventory management needs, granting behavior, and backend architecture.

Always handle and verify purchase events server-side, never on the client.

Option 1: ConfirmPayment (Pre-authorization validation)

With ConfirmPayment, Stash calls your backend before finalizing the charge. This gives your game server a chance to:

  • Validate the purchase (inventory limits, purchase locks, eligibility)
  • Optionally grant rewards as part of that validation
  • Approve or reject the transaction

Best for: Games with per-player inventory limits, multi-client locking, or other validation that must happen before a purchase is finalized.

How it works:

  1. Player completes checkout
  2. Stash sends a ConfirmPayment request to your backend
  3. Your backend validates and optionally grants rewards
  4. Your backend responds with Approve or Reject
  5. Stash finalizes or cancels the charge accordingly

For implementation details including HMAC signature verification, see the Authentication guide. For the full request/response schema, see the ConfirmPayment API Reference.

Option 2: PURCHASE_SUCCEEDED Webhook (Async Post-Purchase)

Webhooks are automated notifications that Stash sends to your backend after a purchase is completed. This fits an event-driven architecture where your backend consumes events and processes them asynchronously.

Best for: Backends that process events asynchronously (queues, workers, background jobs), where it's acceptable for rewards to appear a bit later in the game.

When a purchase event is triggered, Stash sends an HTTP POST request with a JSON payload to the webhook URL you've configured in Stash Studio.

How it works:

  1. Player completes checkout
  2. Stash finalizes the payment
  3. Stash sends a PURCHASE_SUCCEEDED webhook to your backend
  4. Your backend processes the event (e.g., via a queue or worker)
  5. Your backend grants rewards and updates your systems

Stash Pay Webhook Events

Stash Pay sends the following webhook events:

Required Events:

  • PURCHASE_SUCCEEDED: Sent when a purchase completes successfully. The source field will be "StashPay". This is the primary event for granting items to players.

Optional Events:

  • CREATE_PAYMENT_INTENT: Sent when a payment intent is created (user initiates checkout). Useful for tracking checkout initiation and payment method selection.
  • FREE_ITEM_REDEEMED: Sent when a user redeems a free item (promotional items, rewards, etc.).

Example webhook message payload

{
  "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 will always be "StashPay" for events originating from Stash Pay integrations.

For complete webhook implementation details, see our webhook documentation:

Option 3: GetPaymentEvent (Synchronous Post-Purchase)

You can retrieve purchase status by calling the GetPaymentEvent endpoint from your backend. This fits a synchronous pattern where the client expects a definitive answer right after purchase.

Best for: Games where the client needs rewards shown immediately after purchase, backends designed for synchronous request–response patterns, or when you prefer to avoid webhooks.

How it works:

  1. Player completes checkout
  2. Your backend calls GetPaymentEvent with the purchase ID
  3. Stash returns the final status of the payment
  4. If successful, your backend grants rewards and returns updated state to the client

Send a GET request to retrieve payment information:

Query Purchase Status
GET https://test-api.stash.gg/sdk/server/payment/<payment_id>

For complete endpoint documentation, see GetPaymentEvent API Reference.

Combining patterns: You can combine these patterns. For example, use ConfirmPayment for pre-purchase validation and GetPaymentEvent for client-side confirmation of the final state.

Handling Failures and Edge Cases

Regardless of which pattern you choose, follow these best practices:

Idempotency

Make your reward-granting logic idempotent by purchase ID so retries (from webhooks, polling, or ConfirmPayment retries) never grant rewards twice. Store granted purchase IDs and check before granting.

Timeouts and Retries

  • For ConfirmPayment: Define clear behavior when your backend is slow or unavailable (e.g., reject, retry, or show a recoverable error in-game)
  • For webhooks: Implement retry handling and signature verification. See Webhook Retries for retry behavior details
  • For GetPaymentEvent: Handle transient failures (e.g., retry with backoff) and show appropriate messaging to the player

Testing your integration

To test your Stash Pay integration, use test card numbers that work with Stash's test environment. These test cards allow you to complete transactions without creating real charges, making it safe to test your integration repeatedly.

Environment Requirements:

  • Test/Development/Staging: Use test cards only. Test cards work in test environments and will be rejected in production.
  • Production: Use real, live payment cards only. Real cards are required for production transactions and will be rejected in test environments.

Test cards work with both browser-based and in-app checkout flows. When testing:

  • Use test cards in your development and staging environments only
  • Verify that webhooks are received correctly for test transactions
  • Test both successful and failed payment scenarios
  • Confirm that your server properly verifies and grants items after test purchases
  • Always verify you're using the correct environment (test vs production) before processing transactions

For a complete list of test cards and testing guidelines, see Test Card Numbers.

Testing Tools

Link Generator: Use the Link Generator in Stash Studio to quickly generate and test checkout links without writing code. The Link Generator allows you to:

  • Generate checkout links with a visual form interface
  • Preview checkouts in an iOS mobile frame
  • Copy CURL commands for automated testing
  • Save and share test configurations with your team
  • Automatically detect test vs production environments

How is this guide?