Signing Customers In
Every customer-specific call on the Storefront API needs a customer token in the Authorization: Customer TOKEN header. This page covers how to get one for a person who just arrived on your site. The right flow depends on how their identity can be proven.
Which flow applies to you
| Store platform | How identity is established | Flow |
|---|---|---|
| Steam (Rust, GMod etc) | Steam OpenID sign-in on your site | Server-side flow |
| Minecraft (Java, Bedrock, Geyser) | The customer types their username | Direct flow |
| Anything else | Your own auth, then a platform ID you trust | Server-side flow |
The distinction is whether PayNow can accept an identity claim at face value.
A Minecraft username is what the hosted webstore asks for too, so the platform lets a browser claim one directly. A Steam ID is not, because anyone can type someone else's, so the claim has to be proven by Steam and the token minted from your server.
Server-side flow
Proving that a visitor owns a Steam account is your job, not PayNow's. Implement Steam OpenID on your server, or use an existing library for your framework, and only continue once Steam has confirmed the sign-in is valid. The result you need from it is the customer's SteamID64.
Never mint a token for a SteamID64 that came from the browser without Steam's confirmation.
With a verified SteamID64 in hand, two calls to the Management API get you a token: find or create the customer by SteamID64 (lookup returns 404 for a customer who has never been seen), then create a customer token for them. Store the token in an httpOnly cookie.
import { createManagementClient, isPayNowApiError } from "@paynow-gg/typescript-sdk";
const management = createManagementClient({
apiKey: process.env.PAYNOW_API_KEY!,
storeId: process.env.PAYNOW_STORE_ID!,
});
async function customerIdForSteam(steamId: string): Promise<string> {
try {
const existing = await management.customers.lookupCustomer({ steam_id: steamId });
return existing.id;
} catch (error) {
if (!isPayNowApiError(error) || error.status !== 404) {
throw error;
}
}
const created = await management.customers.createCustomer({ steam_id: steamId });
return created.id;
}
async function customerTokenForSteam(steamId: string): Promise<string> {
const customerId = await customerIdForSteam(steamId);
const { token } = await management.customers.createCustomerToken(customerId);
return token;
}The same shape works for any platform where you already know a trustworthy identifier - swap steam_id for minecraft_uuid or xbox_xuid when creating and looking up the customer.
PayNow fills in the customer's Steam profile name and avatar for you. Read them back from GET /v1/store/customer with the new token - you do not need a Steam Web API key.
Direct flow
Minecraft stores can skip the server entirely. POST /v1/store/customer/auth takes a platform and an ID, creates the customer if needed, and returns a token. It is called without any Authorization header, with only x-paynow-store-id, so a browser can call it directly.
const response = await fetch("https://api.paynow.gg/v1/store/customer/auth", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-paynow-store-id": storeId,
},
body: JSON.stringify({ platform: "minecraft", id: username }),
});
const { customer_token } = await response.json();Only Minecraft-based platforms are supported on this route. Every other platform, including steam, uses the server-side flow above.
Holding the token
Tokens are authentication secrets - whoever has one can act as that customer, including checking out. Treat them like a session.
- Store them in an httpOnly, SameSite cookie set by your server, not in
localStorage. - Mint one per sign-in rather than reusing a long-lived token across devices.
- On sign-out, clear the cookie. To end every session a customer has, call
DELETE /v1/stores/{storeId}/customers/{customerId}/tokensfrom your server. - When a storefront call answers
401or403, treat the customer as signed out and start the flow again.
Using the token
Create a storefront client with the token for every customer-specific call. GET /v1/store/customer is the cheapest way to confirm it is still valid and to get the profile for your header.
import { createStorefrontClient } from "@paynow-gg/typescript-sdk";
const storefront = createStorefrontClient({
storeId: process.env.PAYNOW_STORE_ID!,
customerToken,
});
const customer = await storefront.customer.getStorefrontCustomer();The profile object carries the platform, display name and avatar URL regardless of platform, so your UI does not need to branch on Steam versus Minecraft.