Phoenix Prediction Docs

Idempotency

Prevent duplicate wallet movement when Phoenix retries

Wallet operations can be delivered more than once. If your backend processes the same idempotency key twice, players can be charged, reserved, released, captured, or credited incorrectly.

Duplicates must be successful no-ops

When a completed operation repeats with the same idempotency key and the same request fingerprint, return the stored result and do not change the player balance again.

Scope

Idempotency is scoped by:

operator_id + environment + operation + idempotency_key

Retries must reuse the same key. A new key means a new intended money movement.

Required Behavior

For every money-moving operation:

Verify the Phoenix signature.

Start a database transaction.

Look up the idempotency key and operation in your wallet transaction table.

If the same key and same request fingerprint completed before, return the stored result.

If the same key is still processing, return 409.

If the same key is reused with a different request fingerprint, return 422.

Lock the player balance or reservation rows, or use an atomic balance update.

Apply reserve_cash, release_cash, capture_cash, or credit_cash once.

Store the request body hash, response body, final balance, operation status, and processed timestamp.

Commit.

Store the Received Key

You do not construct idempotency keys. Phoenix generates the key and sends it in the lowercase idempotency-key header on every money-moving request (POST /wallet/transactions). Store the key Phoenix sent and dedup on it.

Treat the key as opaque

Never parse the idempotency key or branch on its internal format. The key is a stable platform-generated string; its shape is internal and not a contract. Key your dedup on the tuple, not on anything you decode out of the key.

To reconcile a move back to a Phoenix order, join on the small references set Phoenix sends in the body, not on the key:

Operationreferences keys
reserve_cash, capture_cash, release_cashorder_id
credit_cash for sell proceeds or maker rebatetaker_order_id, maker_order_id
credit_cash for settlement payoutclaim_side

A correction is a new credit_cash (or capture_cash) move with its own reason, idempotency key, and references. See Reconciliation for the full join model.

Request Fingerprint

Store a fingerprint for the first request body:

sha256(canonical_json(request_body))

The same key with the same fingerprint returns the first completed result. The same key with a different fingerprint returns 422.

Status Lookup

When Phoenix receives an uncertain timeout, it calls POST /wallet/transactions/status with the same envelope as the original money move (same operation, idempotency_key, amount, reason, references), just sent to the status path. Look the operation up by its idempotency key and report its current state.

The status endpoint must not move money. It should report whether the original operation is processing, accepted, rejected, or unknown.

Your ledger should make these states clear:

StateMeaning
processingThe first request is still being handled
acceptedThe wallet operation completed successfully
rejectedThe wallet operation reached a terminal business rejection
unknownThe wallet cannot yet prove the result

SQL Pattern

Key the lookup on the idempotency key Phoenix sent, scoped by (operator_id, environment, operation, idempotency_key).

BEGIN;

SELECT * FROM wallet_transactions
WHERE operator_id = $1
  AND environment = $2
  AND operation = $3
  AND idempotency_key = $4
FOR UPDATE;
-- if found with same fingerprint and completed, return stored response
-- if found with different fingerprint, return 422
-- if found and processing, return 409

SELECT balance FROM players WHERE external_id = $5 FOR UPDATE;
-- validate and update balance once

INSERT INTO wallet_transactions (
  operator_id,
  environment,
  operation,
  idempotency_key,
  external_id,
  amount_value,
  amount_scale,
  currency_code,
  request_fingerprint,
  response_json
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);

COMMIT;

Use your own schema, but keep the same behavior: one received idempotency key, one wallet effect.