> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zerodash.studio/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors and retries

> Which status code to return, what Zero-Dash does with it, and why 422 and 500 are not interchangeable.

The status code you return is a **contract**. It tells Zero-Dash whether a transaction exists on your side, and that decides whether we send a correction.

## Error body

Wherever an error can carry an explanation, use this shape:

```json theme={null}
{
  "message": "insufficient balance"
}
```

Be specific. `"currency mismatch: account is USD, request is EUR"` closes a ticket; `"error"` opens one.

## Status codes on debit

This is the table that matters most.

| Code  | Meaning                                             | What Zero-Dash does                     |
| ----- | --------------------------------------------------- | --------------------------------------- |
| `200` | Bet accepted, stake subtracted                      | Round proceeds                          |
| `400` | Invalid payload format                              | Fails the bet. Include an explanation.  |
| `409` | **Insufficient balance**                            | Fails the bet cleanly, tells the player |
| `422` | Transaction **certainly not created**               | Nothing. **No rollback is sent.**       |
| `500` | Transaction **was created**, then an error occurred | Sends a rollback, with retries          |

<Warning>
  **`422` and `500` are opposites, and the difference is money.**

  Return `422` only when you are certain nothing was written — a validation failure before any database work, a wallet service that refused the connection. We will not roll it back, because there is nothing to roll back.

  Return `500` when the transaction may exist — the write committed and the response failed, a timeout after the balance moved, an unknown state. We will send a rollback and retry it until it succeeds.

  Get it backwards and you either roll back a bet that never existed, or you leave a stake permanently deducted from a player who never played.
</Warning>

```mermaid theme={null}
flowchart TD
    A["Debit arrives"] --> B{"Did anything<br/>get written?"}
    B -->|"Definitely not"| C["422<br/>no rollback follows"]
    B -->|"Yes, or unknown"| D["500<br/>rollback follows"]
    B -->|"Wrote it, all good"| E["200<br/>round proceeds"]
    B -->|"Not enough funds"| F["409<br/>bet declined"]
```

<Tip>
  When you genuinely cannot tell, return `500`. A rollback for a transaction you never created is answered with `404` and costs nothing. A stake stranded on a player's account costs a support ticket and a manual correction.
</Tip>

## Status codes on credit and rollback

| Code  | Meaning                          | What Zero-Dash does                            |
| ----- | -------------------------------- | ---------------------------------------------- |
| `200` | Applied                          | Round closed                                   |
| `400` | Invalid payload format           | Retries — fix the cause, it will keep arriving |
| `404` | Referenced transaction not found | Escalates to manual review                     |
| `500` | Could not process                | Retries with backoff                           |

<Note>
  There is no `422` here. A settlement always has to land: if it cannot be applied now, it is retried until it can, or a human looks at it.
</Note>

## Retry behaviour

Zero-Dash uses **durable retry logic** for failed credit and rollback calls.

<Columns cols={2}>
  <Card title="Credit" icon="arrow-down-to-arc">
    **10 retries**, exponential backoff, spanning **3 days**.
  </Card>

  <Card title="Rollback" icon="arrow-rotate-left">
    **10 retries**, exponential backoff, spanning **3 days**.
  </Card>
</Columns>

If all retries fail, Zero-Dash keeps retrying until the settlement resolves or the case is manually reviewed. A round is never silently abandoned.

<Warning>
  **Credit and rollback must not require a player session token.**

  Requests can arrive days after the round finished. You must process them whether or not the player is logged in — resolve the player from `playerId` alone and apply the movement.

  Session-gated settlement endpoints are the most expensive integration bug we see: every retry fails, the round stays open for three days, and the player is missing a payout the whole time.
</Warning>

## Reconciling after a failure

When a settlement keeps failing, or a debit ended in an ambiguous `500`, [Get transaction](/api-reference/callbacks/get-transaction) is how state gets compared.

```http theme={null}
GET /players/acct_8f2c19/transactions/test-transaction-1112
X-Zd-Signature: …
X-Zd-Timestamp: 1778920901644
```

| Your answer                | What it tells us                       |
| -------------------------- | -------------------------------------- |
| `200` with the transaction | It exists. Settle against it.          |
| `404`                      | It does not exist. Nothing to correct. |
| `500`                      | Your side is unavailable. We retry.    |

<Warning>
  Return `404` only when the transaction genuinely does not exist. A `404` returned because the lookup itself failed reads as "this bet never happened" and closes the round the wrong way.
</Warning>

## Designing for the retry window

<Steps>
  <Step title="Make settlements terminal" icon="lock">
    Once applied, a credit or rollback stays applied. A later duplicate replays the stored result — see [Idempotency](/wallet/idempotency).
  </Step>

  <Step title="Never expire an open bet" icon="hourglass">
    A debit can sit unsettled for three days. Do not sweep old pending transactions on a shorter timer; a cleanup job that voids them locally will disagree with our ledger.
  </Step>

  <Step title="Log the Zero-Dash IDs" icon="file-lines">
    Store `transactionId`, `referenceTransactionId` and `gameRoundId` on every row. They are the only keys we can search by during an incident.
  </Step>

  <Step title="Alert on repeated 4xx" icon="bell">
    A `400` on a settlement will keep arriving until you fix it. Catch that in your own monitoring rather than in a support thread.
  </Step>
</Steps>

## Failure playbook

<AccordionGroup>
  <Accordion title="Every callback returns 401" icon="key">
    A signature problem, and it is almost always the path or the raw body. Work through [Troubleshooting](/security/signature#troubleshooting) — verify against the path you registered, including any prefix, and against the untouched request bytes.
  </Accordion>

  <Accordion title="Credits pile up unsettled" icon="layer-group">
    Your `/credit` handler is rejecting something systematically. The usual causes: zero payouts treated as invalid, a required session token, or a `referenceTransactionId` lookup that fails for free rounds.
  </Accordion>

  <Accordion title="Balances drift from ours" icon="scale-unbalanced">
    Duplicate application. Check that `transactionId` is the primary key of your ledger and that the balance update shares a database transaction with the insert.
  </Accordion>

  <Accordion title="A player is missing a stake" icon="user-minus">
    A debit that returned `422` after actually writing. We never rolled it back, because you told us there was nothing to roll back. Audit which code path returns `422` and confirm nothing can be written before it.
  </Accordion>
</AccordionGroup>
