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.

Prerequisites

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.

Step-by-step integration guide

1. 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.

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 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.

2.1 Checkout completion

Upon checkout completion, Neon will redirect the user to the successUrl or cancelUrl set in POST /client/checkout.

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 API described in the next steps.

📘

successURL can be a deeplink that brings the user back to the game.

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

🚧

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

3.1 Optional: 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.

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 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.

🚧

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

4. Verify Signed Responses from Neon

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)

4.1 Typescript: Signature verification example

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

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

📘

Refer to the Unity signature verification example here