Serverless Integration (beta)

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

🚧

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.

🚧

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 .


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.

Step-by-step guide

Set up offers on your Storefront

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.

Get a client token for a game account

Use POST /client/token 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.

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 using the client auth token.

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 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).
  • Some fields are optional (see the API documentation 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 for a list of the countries Neon supports, and which currencies we support in each.

Checkout completion

Upon checkout completion, Neon will redirect to the successUrl or cancelUrl you provided to your checkout.

If the checkout was processed successfully, we create a purchase object. The successUrl will include a purchaseId appended as a query parameter for you to save and use.

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 endpoint with the client token and the purchaseId parsed from the successUrl.

⚠️ Be sure to verify the signed response body before using the response body.

Get unclaimed purchases for the account

Use the GET /client/purchases/unclaimed API with the client token to retrieve a list of unclaimed purchases associated with the account.

⚠️ Be sure to verify the signed response body before using the response body.

Response signatures

Some /client endpoints (e.g. PATCH /client/purchase/{purchaseId}/claim) 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_bodyis exact stringified JSON)
  4. Verify primary signature: Attempt verification 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 developer dashboard
  • Handle key rotation gracefully by supporting both primary and deprecated signatures during rotation periods
  • Store public keys in your application's secure configuration (they're public, but should still be protected)

Signature verification example

This example only verifies the signature using the public key. It does not include other checks like timestamp freshness.

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