Skip to content
Skip the menu

Server SDKs

Server SDKs

Pick the GameCoin SDK for your backend language (Node.js, Python, PHP, Go or C#) and learn what the five have in common and where they differ.

View as Markdown

On this page

A server SDK is the quickest way to call the GameCoin server API from your game backend. It signs every request with your secret key, keeps writes safe to retry, and turns errors into typed values, so that you write wallet.grant(...) instead of building HTTP requests by hand. Each SDK is a thin layer over the API reference: everything it does, you could do with curl.

Warning

A secret key can credit coins, grant items and refund orders for every player of your game. It belongs on your server, never in a browser, a mobile app or a game client. For code that runs on the player's device, use the browser SDK with a publishable key: it can read, spend and pay for packs, and it can never credit anything by itself. The one exception is a gift code that your studio created: it gives free coins and items, once per player, and the browser SDK can redeem it. Your server hands it a short-lived player token, which the SDKs create with players.createToken.

Pick your SDK

LanguagePackageNeedsDependenciesPage
Node.js and TypeScript@apilow/gamecoinNode.js 18 or laterNone (built-in fetch)Node.js SDK
Pythonapilow-gamecoin (import gamecoin)Python 3.9 or laterNone (standard library)Python SDK
PHPapilow/gamecoinPHP 8.1 or later, with curl and jsonNone beyond those two extensionsPHP SDK
Gogithub.com/apilow/gamecoin-goGo 1.21 or laterNone (standard library)Go SDK
C# and .NETApilow.GameCoin.NET 8, or a runtime that supports .NET Standard 2.1None on .NET 8, System.Text.Json on .NET Standard 2.1C# SDK

Note

The SDKs are a pre-release (version 0.1.0) and are not published yet: they are not on npm, PyPI, Packagist, the Go module proxy or NuGet. The package names above are the planned ones and may change before the first release. Each page gives the install command that will work once the package exists.

Your language is not on the list? Call the API directly. The SDKs add no hidden behaviour: what they do beyond the raw API is listed on this page.

The same start in five languages

Grant a new player 100 gems, spend 30, and start the sale of a pack. The player is ext("user-42"): your own id for the player, which GameCoin creates on first use. The key is read from the GAMECOIN_SECRET_KEY environment variable.

LangageLanguage
import { GameCoin, ext } from "@apilow/gamecoin";

const gamecoin = new GameCoin(process.env.GAMECOIN_SECRET_KEY, { baseUrl: "https://gamecoin.apilow.com" });
const player = ext("user-42");

await gamecoin.wallet.grant(player, { currency: "gems", amount: 100, reason: "welcome_gift" });
const { balance } = await gamecoin.wallet.spend(player, { currency: "gems", amount: 30, reason: "revive" });
console.log(balance.total); // 70

const { order, checkoutUrl } = await gamecoin.checkouts.create(player, { packSku: "gems-500" });
console.log(order.status, checkoutUrl); // pending, then send the player to checkoutUrl

https://gamecoin.apilow.com is the address of this service, which is also the default of every SDK: you only need to pass a base URL to point an SDK at another address. The test environment simulates the payment: the player opens checkoutUrl, and the hosted page, in French for now, offers « Payer » and « Refuser » buttons. The Go version is longer because Go returns every error to you, and a program has to say what to do with each.

Check the signature of a webhook

Since version 0.2.0 each SDK checks the signature of a webhook for you. It is a plain function: it needs no client and makes no request. Give it the raw body of the request, the GameCoin-Signature header and the signing secret of the endpoint (whsec_gc_…). It returns the event, or fails with a signature error that has a reason: missing_header, malformed_header, no_matching_signature, timestamp_out_of_tolerance or invalid_payload. A call older than 5 minutes is refused; every SDK lets you change that.

LangageLanguage
import { webhooks, WebhookSignatureError } from "@apilow/gamecoin";

// With Express: app.post("/gamecoin", express.raw({ type: "application/json" }), (req, res) => { … })
const event = webhooks.constructEvent(req.body, req.get("GameCoin-Signature"), process.env.GAMECOIN_WEBHOOK_SECRET);
console.log(event.type, event.id);

Read the body before any JSON parsing: a parsed and serialized body can differ by one character, and the check then fails. Each language page has a complete example.

Language of the payment page

checkouts.create takes an optional locale (fr, en or nl): the language of the hosted payment page, of the receipt and of the e-mails of the order. Leave it out and the page follows the browser of the player.

What the five do the same way

The five SDKs follow one specification. They cover the whole server API with the same eight resources, catalog, players, wallet, ledger, inventory, purchases, checkouts and orders, and the same sixteen calls. These behaviours do not change from one language to the next.

Writes are idempotent

Every call that changes something sends an Idempotency-Key: wallet.grant, wallet.spend, inventory.grant, inventory.consume, purchases.create and checkouts.create. When you give no key, the SDK generates a UUID v4 once per call, and every retry of that call reuses it. A retry can therefore never credit a player twice. To make a retry of your own code safe too, pass a key of your choosing, from 1 to 100 printable ASCII characters. The result of these calls carries a boolean replayed, which is true when the API answered from an earlier request with the same key. See Idempotency.

Failed calls are retried

By default an SDK retries twice, so a call makes at most three attempts. You can set this from 0, which turns retries off, to 10.

  • What is retried: a network error, a timeout, a 5xx and a 429. Reads, players.upsert and players.createToken follow the same rules as the writes.
  • What is not: any other 4xx, an answer that is malformed although its status was 200, and a call that you cancelled.
  • How long it waits: 0.5 s × 2^attempt, multiplied by a random factor between 0.75 and 1.25. A Retry-After header wins, on a 5xx as well as on a 429, up to 30 seconds. Beyond that the SDK does not wait: it raises the error and retryAfter tells you how long the API asked for.

A refund is retried on 429 only

orders.refund has no idempotency key, so a blind retry could refund twice. The SDK retries it on 429 only. After a network error, a timeout or a 5xx, the call fails and the outcome is unknown: read the order back with orders.get, and look at its status, before you try again.

One error type, with a code

Everything that goes wrong with a request is one error type, with the same five pieces of information: a code, the HTTP status, a message, the details of the API and a retryAfter. The code is the API code (INSUFFICIENT_FUNDS, NOT_FOUND…), or one of three codes the SDK raises when no usable answer exists: NETWORK_ERROR and TIMEOUT (status 0), and INVALID_RESPONSE. A code that the SDK does not know yet is passed through as it is. For INSUFFICIENT_FUNDS, details holds required and available. The codes are listed in Errors, and each SDK page gives the ones you will meet.

Arguments are checked before any request

An empty player, a negative amount, a quantity that is not an integer, a page size outside 1 to 100, an idempotency key that is not 1 to 100 printable ASCII characters: the call fails at once, with the usual argument error of the language, and nothing is sent. This error is not the SDK error type, and its message never contains the offending value.

Your players are yours to name

Any string works as your own id for a player, through the ext helper: spaces, slashes, % and accents included. The SDK URL-encodes the reference exactly once. With a secret key, a write on an unknown ext player creates it. A read answers NOT_FOUND.

The key stays hidden

An SDK accepts only a secret key (gc_sk_test_… or gc_sk_live_…). A publishable key gets a message that points to the browser SDK. The key is trimmed of surrounding white space, and the SDK reads the environment, test or live, from its prefix. It never appears in an error message, in a log line or in the string form of the client, which shows gc_sk_test_… only.

Plain objects, native dates

The results mirror the objects of the API reference field for field. Timestamps are the date type of the language, amounts are integers, and a field that an SDK does not know yet is ignored, so a newer API does not break an older SDK. Every request carries a User-Agent such as gamecoin-node/0.1.0 (node/22.0.0), which names the SDK and its version.

What changes from one language to the next

The behaviours above are the same everywhere. What differs is the way each SDK fits its language. The first table covers the shape of the calls.

Node.jsPythonPHPGoC#
NamescamelCase (packSku)snake_case (pack_sku)camelCase methods and properties, snake_case array keys (pack_sku)PascalCase, initialisms in capitals (PackSKU, CheckoutURL)PascalCase, Async suffix (GrantAsync)
ParametersAn objectKeyword argumentsAn arrayA struct (GrantParams)A record (GrantRequest)
Your own player idext("id")ext("id")PlayerRef::ext('id')gamecoin.Ext("id")PlayerRef.Ext("id")
AsynchronousYes, promisesNo, synchronous onlyNo, synchronousNo, synchronous and safe to share between goroutinesYes, async and await
Cancel a callAn AbortSignalNot possibleNot possibleThe context.ContextA CancellationToken
Read the whole ledgerfor await over ledger.iteratefor over ledger.iterateforeach over ledger->iterate()The iterator of Ledger.Iterate: Next, Entry, Errawait foreach over Ledger.IterateAsync
Pass your own idempotency key{ idempotencyKey } as the last argumentidempotency_key=['idempotency_key' => …] as the last argumentThe gamecoin.WithIdempotencyKey(…) call optionA RequestOptions
Replace the HTTP layerThe fetch optionA transport functionA TransportInterfaceWithHTTPClient with your own RoundTripperAn HttpMessageHandler

The second table covers values and errors.

Node.jsPythonPHPGoC#
Unit of the timeout optionMillisecondsSeconds, for each socket operationSecondsA time.DurationA TimeSpan
Type of retryAfterA number of seconds, or undefinedSeconds, or NoneSeconds, or nullA time.Duration, zero when absentA TimeSpan?
Error typeGameCoinErrorGameCoinErrorGameCoinException*gamecoin.ErrorGameCoinException
Where the code iserror.codeerror.code$e->errorCodeerr.Code, or gamecoin.IsCode(err, …)ex.Code
Wrong argumentTypeError or RangeErrorTypeError or ValueErrorInvalidArgumentExceptionAn error that wraps ErrInvalidArgumentArgumentException
Clear a profile fieldSet it to nullSet it to NoneSet the key to nullList it in ClearList it in Clear
Leave an optional value outOmit the fieldOmit the argumentOmit the keyThe zero value ("", 0, nil)Leave the property null
DatesDatedatetime, with a time zoneDateTimeImmutabletime.TimeDateTimeOffset

Two small differences are worth knowing. PHP keeps the API code in errorCode, because getCode() has to be an integer and returns the HTTP status. In Go, a zero value means "not set", so Quantity: 0 is a quantity of 1 and a ledger Limit: 0 is the default page size.

Status

The SDKs are a pre-release, version 0.2.0. The GameCoin API runs in the test environment, with simulated payments or Stripe in test mode: real payments are not available yet, and the live environment is reserved for studios that Apilow has verified. See Keys and environments. Until the first release, the API of an SDK may change between minor versions, and so may the name of its package.