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.
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.
Apple Pay and Google Pay do not work in the test environment without individual setup. Both require sandbox accounts. Contact your Stash representative to provision them for your test environment if needed.
Create a checkout link
To create a checkout session, send a POST request from your backend to the /sdk/server/checkout_links/generate_quick_pay_url endpoint.
Please check out the full API reference to see all the payload options.
Checkout link request
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": ""
}| Parameter | Type | Description |
|---|---|---|
| item | object | The 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. |
| user | object | Information 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. |
| transactionId | string | Unique transaction identifier generated by your backend for idempotency and tracking. |
| regionCode | string | (optional) Region/country code for payment localization (e.g., "US"). |
| currency | string | ISO 4217 currency code for the transaction (e.g., "USD", "EUR"). |
Checkout link response
If your request succeeds, you will receive a checkout URL that you can present to the user using any of the methods described below.
{
"url": "https://store.example.com/order/abc123",
"id": "abc123",
"regionCode": "US"
}Authentication
Authenticate calls to generate_quick_pay_url by signing the request body with your ingress secret and including the signature in the x-stash-hmac-signature header. Your ingress secret is distinct from the egress key used to verify Stash's outbound requests — both are in Studio → Project Settings → API Secrets as base64-encoded values; base64-decode before use as the HMAC key.
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 JSON bytes you POST. Sign what you transmit — Stash verifies against the bytes received. Requests where <unixMillisTimestamp> is more than 5 minutes from the server clock are rejected.
For Node.js, Python, and Go implementation code, see API Keys → HMAC Signing.
Shops and API keys created before 2026-08-15 can still use X-Stash-Api-Key: <secret> instead. Keys created on or after 2026-08-15 must use versioned HMAC.
Displaying the checkout
The simplest option is to open the checkout URL directly in a browser on the user's device. For in-app presentation with native drawers, modals, and callbacks, follow the platform-specific integration guide for your stack.
Web Apps & WebGL
React component, vanilla API, and UMD script-tag integration.
Native Apps (iOS / Android)
Card, modal, and browser presentation using the Stash Native SDK.
Unity Integration
Present checkout from Unity projects using the Stash Unity package.
Unreal Engine Integration
Present checkout from Unreal Engine projects using the Stash plugin.
Processing Purchase
Once a player completes checkout, your system must process the event to grant items. Stash supports three patterns depending on your game's requirements, always server-side, never on the client.
The three differ mainly in when they fire relative to the charge, and only one of them lets your server reject a purchase:
| Pattern | When it fires | Direction | Can your server reject? | Best for |
|---|---|---|---|---|
ConfirmPayment | Before the charge is captured | Stash calls your server | Yes. See the warning in that tab. | Inventory limits, purchase caps, multi-client locking |
PURCHASE_SUCCEEDED webhook | After the payment completes | Stash calls your server | No | Backends that process events asynchronously through queues, workers, or jobs |
GetPaymentEvent | After the payment completes, when you ask for it | Your server calls Stash | No | Showing rewards in the client immediately after purchase |
This is an integration-time choice, not a runtime one. Which delivery mechanism a shop uses is fixed when the shop is configured, not selected per purchase, so a single shop uses one of these patterns rather than switching between them. Pick the row that matches your backend, and see the High-Level Flow Options guide for the full sequence of each.
Async post-purchase. Stash sends your backend a PURCHASE_SUCCEEDED webhook after the payment completes.
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.
Configure your webhook endpoint in Stash Studio and implement a listener that verifies the signature and grants rewards. See the webhook guides for full implementation details:
- Webhooks Overview: configure webhooks in Stash Studio
- Webhook Listener: create a listener and verify signatures
- Webhook List: all event types and payload structures
- Webhook Retries: retry behavior and idempotent handlers
Synchronous post-purchase. Your backend asks Stash for the final payment status as soon as the purchase completes.
This is a targeted lookup, not a background poll. The trigger is the client's completion signal: when the client learns that the purchase completed, it tells your backend, and your backend calls GetPaymentEvent once. What that signal is depends on how you present the checkout:
- Web: the
onSuccessprop or option. See Web Apps. - Unity: the
successCallbackargument ofOpenCardorOpenModal, for examplesuccessCallback: OnSuccess. See Unity. - Browser presentation modes (
openBrowser: Safari View Controller, Chrome Custom Tabs, or the system browser): there is no success callback. The player returns to your game through the deep link, and that return is the signal. See Presentation Options.
Your backend already holds the checkout link order ID from creating the link, so it can look the purchase up as soon as the client reports completion.
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:
- Player completes checkout.
- The client receives the completion signal (the SDK success callback, or the deep-link return in browser modes) and notifies your backend.
- Your backend calls
GetPaymentEventwith the purchase ID. - Stash returns the final status of the payment.
- If successful, your backend grants rewards and returns updated state to the client.
GET https://test-api.stash.gg/sdk/server/payment/<payment_id>This is a server-side endpoint. Do not call it from the client. For complete endpoint documentation, see the GetPaymentEvent API Reference.
Validation before the charge is captured. Stash calls your backend with ConfirmPayment before capturing the payment, and waits for your response.
This is the only one of the three patterns where your server can reject a purchase, and it grants items during the same call. It fires earliest of the three.
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:
- Player completes checkout.
- Stash sends a
ConfirmPaymentrequest to your backend. - Your backend validates the purchase, grants the items, and returns them in the response body.
- Stash captures the payment.
There is no approve or deny field. Your server signals success by returning 200 OK with the granted results in the body. It rejects by returning an HTTP error status.
Any failure response fails the purchase. Stash treats every non-success response the same way, whether it is a deliberate rejection, an unhandled 500, a timeout, or a malformed body. When that happens the purchase fails and Stash does not retain the funds.
Respond 200 OK unless you specifically do not want Stash to take the money.
For implementation details including request validation and webhook signature verification, see the Webhook Listener guide. For the full request/response schema, see the ConfirmPayment API Reference.
Testing your integration
To test your Stash Pay integration, use test card numbers in the Stash test environment. These cards allow you to complete transactions safely, as no real charges are created, making repeated testing risk-free.
Environment Requirements:
- Test/Development/Staging: Use test cards only. Test cards function in test environments and will be rejected in production.
- Production: Use real, live payment cards only. Real cards are accepted for production transactions and will be rejected in test environments.
- Apple Pay and Google Pay are not available in the test environment. These payment methods require live mode and will not function when testing.
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:
How is this guide?