---
updatedAt: 2026-05-07T15:47:17.000Z
agentTools:
  projectIndex: https://docs.neonpay.com/llms.txt
---

# Integration Guide: Serverless Checkout

If you don't have a game server, use Neon's suite of tools to manage and track inventory.

<Callout icon="🚧" theme="warn">
  #### If you use Neon to power your storefront:

  Your storefront will automatically handle the initiation and launch of checkout experiences. Please skip to [Updating Your Game](https://docs.neonpay.com/docs/fulfillment/).
</Callout>

<Callout icon="🚧" theme="warn">
  #### &#x20;If you have a game server (includes most direct checkout use-cases):

  You should use the server-side version of our APIs to handle the initiation and launch of checkout. Please go to [Initiating a Checkout](https://docs.neonpay.com/update/docs/creating-a-checkout/) .
</Callout>

***

If your game operates only on the client-side and does not have a server, continue reading. Neon can act as your inventory management system.

# Prerequisites

## Set up offers on the Neon side

Since Neon will manage the inventory that you sell via our storefront, you'll need to go through some of the storefront setup steps to start configuring the items you'd like to fulfill.

* See [Managing Offers](https://docs.neonpay.com/docs/offers/) for information on configuring offers, or configure offers via [Neon Console](https://docs.neonpay.com/docs/integrate-and-manage-offers).

# Step-by-step integration guide

## 1. Get a client token for a game account

Use [`POST /client/token`](https://docs.neonpay.com/reference/createclienttoken/) to generate a client token. This token will be used for future steps that will be tied to a game account, like creating a checkout.

* The token can be re-used until its expiration. Since the token expires, be sure to incorporate retry logic.
* Be sure to pass this token into other `/client` routes.

## 2. Create a checkout for the account

Once your user knows the items they'd like to purchase, you can redirect them to Neon checkout. You'll do so by creating a Checkout object with the data needed to start the checkout process. You'll receive a redirect URL in response, which will take the user to Neon's checkout form. Once the user completes the purchase, they'll be redirected back to the `successUrl` you specified when creating the checkout.

You can start a Neon checkout by creating a Checkout object via [`POST /client/checkout`](https://docs.neonpay.com/reference/createcheckoutclient#/) using the client auth token.

```curl
curl --request POST \
     --url https://api.neonpay.com/client/checkout \
     --header 'X-Client-Token: <client token returned from the auth api>' \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '
{
  "offers": [
    {
      "sku": "<sku>",
      "quantity": <quantity>,
    }
  ],
  "successUrl": "<success URL>",
  "cancelUrl": "<failure URL>",
  "storeUrl": "<store URL>",
  "languageLocale": "<locale>",
  "playerCountry": "<country>",
  "currency": "<currency>",
  "contact": {
    "email": "<email address>"
  },
}
```

* `offers` contains the list of offer objects from your inventory the user wants to purchase:
  * `sku` is a unique code you can use to fulfill the purchase to the user's account. The `sku` should match the inventory you set up in your storefront. See [Managing Offers](https://docs.neonpay.com/update/docs/offers/) for more on how to set up offers.
  * `quantity` is the number of this item they want to purchase.
* `successUrl`, `cancelUrl`, and `storeUrl` are links to your storefront that the user can be redirected to.
  * `successUrl` is the URL the user is sent to once they successfully complete the checkout.
  * `cancelUrl` is the URL the user is sent to if they decide to cancel the checkout at any point.
  * `storeUrl` is the URL the user is directed to after signing up for a Neon Pay account.
* `languageLocale` and `playerCountry` are used to geo-locate the user and offer the appropriate translations and payment methods.
  * `languageLocale` is an IETF BCP 47 tag, e.g. `en-US`, that's used to show the correct language for all text in the checkout form.
  * `playerCountry` is an ISO 3166-1 country code, e.g. `US`, that's used to show the correct payment methods for the user, and also to ensure that the user's input payment method originates from the same country they are physically in.
  * `currency` is the ISO 4217 currency code of this checkout. It must match the currency of the country (see list [here](https://docs.neonpay.com/docs/currencies)).
* Some fields are optional (see the API [documentation](https://docs.neonpay.com/reference/createcheckoutclient#/) for a more extensive list):
  * You can set `externalReferenceId` and `externalMetadata` for use in the fulfillment process. Neon includes them in the purchase completion and in analytics events, but otherwise doesn't make use of them.
  * You can set `contact` to prefill the customer’s email address at checkout.

### Currencies and country codes

You must pass in a valid ISO 3166-1 country code as `playerCountry` to create a checkout. You should also pass in a valid ISO 4217 currency code as `currency`. These two fields serve different purposes:

* In addition to the purposes described above, `playerCountry` is used as the player's billing country. We currently only support one currency per country, and the user must be shown, and charged in, that currency. You can see each supported country's expected currency here.
* You can use `currency` to validate that the currency you show to the user before starting a checkout is the same currency Neon uses. In case of a mismatch, Neon returns an error with the expected currency.

See [International Support](https://docs.neonpay.com/docs/currencies) for a list of the countries Neon supports, and which currencies we support in each.

## 2.1 Checkout completion

Upon checkout completion, Neon will redirect the user to the `successUrl` or `cancelUrl` set in [`POST /client/checkout`](https://docs.neonpay.com/reference/createcheckoutclient#/).

If the checkout was processed successfully, we create a purchase internally and redirect the user to the `successUrl`. The `successUrl` includes a `purchaseId` appended as a query parameter. (e.g. `.../?purchaseId=...`). Your app should then extract this value and use it in the [ `PATCH /client/purchase/{purchaseId}/claim`](https://docs.neonpay.com/reference/clientclaimpurchase#/) API described in the next steps.

<Callout icon="📘" theme="info">
  `successURL` can be a deeplink that brings the user back to the game.
</Callout>

## 3. Mark an account's purchase as claimed

Once the purchase has been made, mark the purchase as claimed by making a request to the [ `PATCH /client/purchase/{purchaseId}/claim`](https://docs.neonpay.com/reference/clientclaimpurchase#/) endpoint with the client token and the `purchaseId` parsed from the `successUrl` in the previous step.

<Callout icon="🚧" theme="warn">
  Be sure to [verify the signed response body](https://docs.neonpay.com/docs/serverless-checkout#response-signatures) before using the response body.
</Callout>

## 3.1 Optional: Get unclaimed purchases for the account

Use the [`GET /client/purchases/unclaimed`](https://docs.neonpay.com/reference/clientgetpurchasesunclaimed#/) API with the client token to retrieve a list of unclaimed purchases associated with the account.

This endpoint is primarily a fallback mechanism. For example, if the player closes the game or app before the claim is completed - in the game you can add a button to "Check for unclaimed items". This would call [`GET /client/purchases/unclaimed`](https://docs.neonpay.com/reference/clientgetpurchasesunclaimed#/) and from it you can build a list of any unclaimed items/purchases, and then allow the user to claim them using the `/claim` API, because from `/client/purchases/unclaimed` API you'll receive id which is `purchaseID` needed for claiming call.

<Callout icon="🚧" theme="warn">
  Be sure to [verify the signed response body](https://docs.neonpay.com/docs/serverless-checkout#response-signatures) before using the response body.
</Callout>

## 4. Verify Signed Responses from Neon

### Response signatures

Some `/client` endpoints (e.g. [ `PATCH /client/purchase/{purchaseId}/claim`](https://docs.neonpay.com/reference/clientclaimpurchase#/)) will contain sensitive data that you'll want to use for inventory fulfillment. To ensure that you can trust these responses come from Neon, we sign the response body with signature headers, which you should validate before you handle the response.

### Signed headers

Every signed response includes the following headers:

* `X-Neon-Signature`: Base64-encoded signature (signed using the main key)
* `X-Neon-Signature-Timestamp`: ISO 8601 timestamp string (prevents replay attacks)
* `X-Neon-Signature-Algorithm`: `Ed25519`
* `X-Neon-Signature-Deprecated`: (Optional) Base64-encoded signature (signed with deprecated key). This header will only be present during a key rotation period.

Be sure to follow best security practices:

* Check signatures for all responses from signed endpoints, not just sensitive operations
* Treat missing or invalid signatures as security violations and do not process the data
* Reject responses with timestamps older than 5 minutes to prevent replay attacks

### Signature verification flow

1. Extract headers: Get `X-Neon-Signature`, `X-Neon-Signature-Timestamp`, `X-Signature-Algorithm` and `X-Neon-Signature-Deprecated` from the response
2. Check the timestamp: Verify the timestamp is recent (within last 5 minutes) and not in the future
3. Reconstruct payload: Create verification string as `{timestamp}.{response_body}` (`response_body`is exact stringified JSON)
4. Verify primary signature: Attempt [verification](https://docs.neonpay.com/docs/serverless-checkout#signature-verification-example) using the public key using the `X-Neon-Signature` header
5. Fallback to verify deprecated signature (optional, relevant during a key rotation): If the primary key fails and `X-Neon-Signature-Deprecated` exists, verify using deprecated public key if available
6. Log verification failures: Monitor failed verifications as potential security incidents

### Public keys

* Public keys are accessible in the Console
* Keys can be rotated here as well
  * We strongly encourage rotating keys regularly as a security best practice
  * Key rotation is done gracefully by supporting both primary and deprecated signatures during a rotation period of 90 days
* Store public keys in your application's secure configuration (they're public, but should still be protected)

## 4.1 Typescript: Signature verification example

:exclamation: This example only verifies the signature using the public key. It does not include other necessary checks like timestamp freshness or key rotation.

```typescript
import * as crypto from 'crypto';

function verifySignature(
  payload: string,
  timestamp: string,
  signatureBase64: string,
  publicKeyPem: string
): boolean {
  try {
    // Reconstruct the signed payload: timestamp.body
    const signedPayload = `${timestamp}.${payload}`;
    const signatureBuffer = Buffer.from(signatureBase64, 'base64');

    // Create public key object from PEM
    const publicKey = crypto.createPublicKey({
      key: publicKeyPem,
      format: 'pem',
      type: 'spki',
    });

    // Verify the signature
    const isValid = crypto.verify(
      null, // Ed25519 doesn't use a hash algorithm
      Buffer.from(signedPayload, 'utf-8'),
      publicKey,
      signatureBuffer
    );

    return isValid;
  } catch (error) {
    console.error('Error during signature verification:', error);
    return false;
  }
}
```

## 4.2 Unity: Signature verification example

<Callout icon="📘" theme="info">
  Refer to the **Unity** signature verification example [here](https://docs.neonpay.com/docs/unity-signature-verification-example)
</Callout>