Neun PredictionsGetting startedIntegration guideErrorsReference

Integration guide: from zero to certified

status: one end-to-end path for a PAM team. Written from ADR-059, ADR-060, ADR-062, ADR-064, ADR-065 and the code. Labels: [BT] built and tested, [FO] built with the flag off, [NB] not built. Items marked "(on branch, not released yet)" are not on origin/integration/final.

1. What you build and what Neun builds

2. The wallet endpoints

Contract file: contracts/openapi/wallet-v1.json. Paths are added to your walletBaseUrl. [BT]

EndpointPurposeBusiness rejection codes (in a signed 200 with status: REJECTED)
POST /v1/debitTake money from the player before a buy, or for a correctionINSUFFICIENT_FUNDS, PLAYER_NOT_FOUND, PLAYER_BLOCKED, SELF_EXCLUDED, RG_LIMIT_EXCEEDED, KYC_REQUIRED, JURISDICTION_BLOCKED, SESSION_INVALID, CURRENCY_MISMATCH, TRANSACTION_LIMIT_EXCEEDED, ACCOUNT_CLOSED, ROLLED_BACK
POST /v1/creditPay the player: sell, settlement, correction. Never refuse for account status.PLAYER_NOT_FOUND, ACCOUNT_CLOSED, CURRENCY_MISMATCH
POST /v1/refundReturn all or part of an earlier successful debitORIGINAL_NOT_FOUND, ORIGINAL_ROLLED_BACK, REFUND_EXCEEDS_ORIGINAL, PLAYER_NOT_FOUND, ACCOUNT_CLOSED, CURRENCY_MISMATCH
POST /v1/rollbackCancel a debit whose outcome Neun does not know. Answer REVERSED or TOMBSTONED.none: never reject a rollback
POST /v1/balanceShow the balance (capability balance, optional)PLAYER_NOT_FOUND, CURRENCY_MISMATCH
GET /v1/healthLiveness. 200 with a signed body, 503 when down.none

Every call carries an operationId. It is the idempotency key. It is the same on every attempt.

Answers:

How Neun signs its calls and how you check them

Request headers: Neun-Wallet-Version, Neun-Key-Id, Neun-Timestamp, Neun-Nonce, Neun-Signature, Neun-Attempt, Neun-Correlation-Id, Neun-Operator-Id.

Check in this order: version is 1, else 400 UNSUPPORTED_VERSION; signature matches (constant time), else 401 INVALID_SIGNATURE; timestamp within 300 seconds (300000 ms, 5 minutes) of your clock, else 401 STALE_REQUEST; nonce not seen in 600 s, else 401 REPLAYED_NONCE.

Sign every response with Wallet-Key-Id, Wallet-Timestamp and Wallet-Signature, bound to the request nonce. The exact formulas and test vectors are in the signing guide.

Secrets: the request key starts with npwq_ and the response key with npws_. They are made in the Back Office and shown once (ADR-064). The HMAC key is the full secret text, prefix included. Keep them in a secret manager. Never put them in code, logs or tickets.

Neun-Timestamp is Unix time in milliseconds. The published wallet-v1.json says seconds by mistake; see section 9.

3. Money rules your wallet must follow

Retries after an unknown outcome

ADR-059 s10.3 is the target. Release 1 does something different today. Open decision: not decided yet (owner row N183).

OperationADR targetWhat Release 1 does today
DEBIT0.5, 1, 2, 4, 8 s within a 30 s window, then ROLLBACKRe-sent within the 30 s window, then rolled back
ROLLBACK1, 2, 5, 10, 30, 60 s, then every 5 minEvery 60 s until final
REFUND (buy)1, 5, 30 s, 2 and 10 min, then every 30 minEvery 60 s until final
CREDIT (sell)same as REFUNDEvery 60 s; a case opens after 5 unknown attempts, retries go on
CREDIT (settlement)same as REFUNDSettlement run backoff: 30 s rising to a 300 s cap

4. Run the conformance kit (W-01..W-27)

The kit tests your wallet. CLI results are informative only. Certification uses hosted runs. [BT] The CLI package @neungaming/wallet-conformance 0.1.0 is published on the Neun GitHub Packages registry. [BT]

Install it from the registry (see the quickstart for the .npmrc setup):

mkdir try && cd try && npm init -y
npm install --ignore-scripts @neungaming/wallet-conformance
npx neun-wallet-conformance --help

No registry access yet? Ask Neun for the tarball and run `npm install --ignore-scripts ./neungaming-wallet-conformance-0.1.0.tgz`.

Run it (secrets only from the environment; use sandbox keys; never production keys):

NODE_ENV=test NEUN_WALLET_REQUEST_SECRET=<your npwq_ sandbox key> NEUN_WALLET_RESPONSE_SECRET=<your npws_ sandbox key> \
neun-wallet-conformance --sandbox-confirmed --allow-remote-sandbox \
  --base-url <your https wallet url> --key-id <request key id> --wallet-key-id <response key id> \
  --operator-id <uuid> --brand-id <uuid> --currency EUR --other-currency USD \
  --funded <ref> --low <ref> --self-excluded <ref> --blocked <ref> --report report.json

You need four test players per currency: FUNDED, LOW, SELF_EXCLUDED, BLOCKED.

Exit codes: 0 every scenario PASS; 1 at least one FAIL; 2 refused or bad usage; 3 no FAIL but at least one SKIPPED.

Report (JSON): { kit, startedAt, target, totals: { PASS, FAIL, SKIPPED }, done, results: [{ id, title, verdict, detail? }], notRun }. done is true only when every scenario passed.

IDWhat it checks
W-01Signed HEALTH; your clock within 5 s of Neun
W-02DEBIT for a funded player
W-03Same DEBIT replayed 3 times, also at once: money taken once
W-04Same operationId, other amount: 409 IDEMPOTENCY_CONFLICT
W-05DEBIT for a low player: INSUFFICIENT_FUNDS, also on replay after top-up
W-06Self-excluded player: SELF_EXCLUDED
W-07Blocked player: PLAYER_BLOCKED
W-08Unknown player: PLAYER_NOT_FOUND
W-09Wrong currency: CURRENCY_MISMATCH
W-10Too many decimals: 400 VALIDATION_FAILED
W-11ROLLBACK of a debit, replayed: money returned once
W-12ROLLBACK first, then the debit: later debit is ROLLED_BACK
W-13DEBIT and ROLLBACK at the same time, 50 times: never double movement
W-14Full REFUND
W-15Partial refunds; the one over the remainder is REFUND_EXCEEDS_ORIGINAL
W-16REFUND of an unknown debit: ORIGINAL_NOT_FOUND
W-17REFUND of a rolled-back debit: ORIGINAL_ROLLED_BACK
W-18CREDIT replayed: paid once
W-19CREDIT to self-excluded and blocked players: both OK
W-20Wrong signature: 401 INVALID_SIGNATURE
W-21Timestamp 301 s old: 401 STALE_REQUEST
W-22Nonce reused within 600 s: 401 REPLAYED_NONCE
W-23Every 200 response is signed and bound to the request nonce
W-24Unknown extra fields are ignored
W-25Load: 1,000 debits, 99% within 1,000 ms, none over 3,000 ms
W-2610 debits at once, balance covers 4: exactly 4 OK, never negative
W-27Plain HTTP is refused or redirected; SKIPPED on a loopback http wallet

5. Receive events

Contract file: packages/contracts/asyncapi/events-v1.json. The list of event types is on the events page. [FO]

6. Certification path

ADR-065 has three environments: SANDBOX (build and self-test), STAGE (certification and joint acceptance), PROD (live).

  1. SANDBOX: run the kit yourself until every W scenario passes.
  2. Hosted run by Neun in SANDBOX or STAGE. [FO] Flag CONFORMANCE_RUNNER_ENABLED, default off.
  3. STAGE: Neun and you run the joint acceptance scripts and both sign the results.
  4. Sign-off: the Neun Integration Lead and your technical owner sign in the Back Office, each with step-up re-authentication.
  5. Only then can PROD credentials be approved and the PROD trading flag switched on.
  6. Hypercare days 1 to 7 at half the order limit.

Wallet and security scenarios cannot be waived.

Validity: until a recertification trigger, for example a new wallet host or 12 months since the last certification. Expiry is behind CERTIFICATION_EXPIRY_ENABLED, default off. [FO]

Runs are signed with HMAC today. An Ed25519 option exists (on branch, not released yet). Signing keys: not decided yet (owner rows N145, N177).

Before real money, Neun needs owner values, names only: PAM_GO_LIVE_REF: not decided yet (owner row N52).

7. Environments

8. Checklist

9. Known differences between the ADR text and today's behaviour

TopicADR textTodayOwner row
Wallet retry cadenceADR-059 s10.3 timingsFixed 60 s or the settlement backoff (section 3)not decided yet (owner row N183)
Operator API key reused with a different bodyADR-060: 409422 IDEMPOTENCY_KEY_REUSEDnot decided yet (owner row N45)
correction.applied partition keyADR-062:95: correctionIdcorrectionId (the internal Kafka key stays marketId, ADR-006:32)none, decided (N187)
Idempotency-Key header in operator-v1ADR-060: requiredRequired at runtime, optional in the schemanot decided yet (owner row N162)
Neun-Timestamp unitADR-059: millisecondsThe published wallet-v1.json on final says Unix seconds by mistake; the ADR and the signer use milliseconds; the contract text is being corrected (not released yet)none