# :icon-sync: Wallet Callback (Seamless)

In Seamless Mode the player balance lives in **your** system. Mangoplay calls the
**Call back endpoint** you supplied on the [Request Form](request-form.md) every time a
bet is placed or settled.

This page is the contract for that endpoint. Getting the idempotency rules right is what
keeps money correct on both sides.

## Request

Mangoplay sends `POST` with a JSON body. Every request carries these headers:

| Header | Value |
| --- | --- |
| `AgentId` | your agent prefix |
| `Security` | your security key |
| `Content-Type` | `application/json` |

The `action` field tells you what happened:

| action | Meaning |
| --- | --- |
| `BET` | Player placed a bet. Debit `validBet` from the player. |
| `SETTLE` | Round finished. Credit `winLose` to the player. |
| `BET_RETURN` | Bet refunded (for example a draw). Return the stake. |
| `CANCEL_SETTLE` | A settled result was cancelled and is being reversed. |

### Fields

Every action carries the same envelope:

| Field | Type | Notes |
| --- | --- | --- |
| `action` | string | `BET`, `SETTLE`, `BET_RETURN` or `CANCEL_SETTLE`. `type` carries the same value. |
| `id` | number | Our request id. Stable across retries of the same call — see [Idempotency](#idempotency--the-important-part). |
| `dealId` | string | **The de-duplication key.** Unique per bill. |
| `betId` | string | Identifies the **round**, not the bill. Not unique per bet. |
| `username` | string | Player name without your prefix. |
| `roundId` | string | Round number, e.g. `HL-B20260813100522`. |
| `game` / `providerName` | string | Game name, e.g. `Hilo`, `Baccarat`, `RollADice`. Both fields carry the same value. |
| `gameType` | string | Always `CASINO`. |
| `betDetail` | string | The hole the player bet on, e.g. `HOLE_3_5`. |
| `validBet` | number | Stake. |
| `turnOver` | number | Turnover for this bill. |
| `winLose` | number | Amount to credit to the player. See the table below. |
| `winLoseWithOutTurnOver` | number | Net profit or loss for the player. See the table below. |
| `gameResult` | string | Outcome. `Wait` on `BET`, a real result on the others. |
| `betTime` | number | Epoch ms when the bet was placed. |
| `settleTime` | number | Epoch ms of settlement. Present from `SETTLE` onwards. |
| `reportDate` | string | Reporting day, `DD-MM-YYYY`. |
| `ip` | string | Player IP. |
| `remark` | string | Free text. Present on `BET`. |

!!! info dealId differs between bet and settle
The bet carries `..._B` and its settlement carries `..._S` — the same bill, two distinct
de-duplication keys. Store both.
!!!

### Money fields

`winLose` is what you credit. `winLoseWithOutTurnOver` is the player's net result, for
your reporting. Both are decided by `gameResult`:

| `gameResult` | `winLose` | `winLoseWithOutTurnOver` |
| --- | --- | --- |
| `WIN` | stake + profit | `winLose` − `validBet` |
| `LOSE` | `0` | `-validBet` |
| `LOSE/HALF` | half the stake returned | `winLose` − `validBet` |
| `DRAW`, `DRAW/RETURN` | `validBet` (stake returned) | `0` |
| `DRAW_RETURN`, `CANCEL_SETTLE` | `0` | `0` |

The last row is not a zero-value transaction. `BET_RETURN` and `CANCEL_SETTLE` ask you to
**reverse a transaction you already applied**, identified by `dealId` — and for
`CANCEL_SETTLE`, additionally by the `id` we echo back (see below). The amount to reverse
is the one from that original transaction, not from this payload.

### Examples

Player bets 100 on Hilo:

```json
{
  "action": "BET",
  "type": "BET",
  "id": 1786615539367226,
  "username": "0tonebx4",
  "dealId": "HL-B20260813100522-1786615539334-QGF2BP31_B",
  "betId": "HL-B20260813100522-HL-B",
  "roundId": "HL-B20260813100522",
  "game": "Hilo",
  "providerName": "Hilo",
  "gameType": "CASINO",
  "betDetail": "HOLE_3_5",
  "validBet": 100,
  "turnOver": 100,
  "winLose": -100,
  "winLoseWithOutTurnOver": -100,
  "gameResult": "Wait",
  "betTime": 1786615539334,
  "reportDate": "13-08-2026",
  "ip": "127.0.0.1",
  "remark": "Waiting for settlement"
}
```

The bet wins and pays even money — profit 100 on a stake of 100. Note the `_S` deal id:

```json
{
  "action": "SETTLE",
  "type": "SETTLE",
  "id": 1786615800702941,
  "username": "0tonebx4",
  "dealId": "HL-B20260813100522-1786615539334-QGF2BP31_S",
  "betId": "HL-B20260813100522-HL-B",
  "roundId": "HL-B20260813100522",
  "game": "Hilo",
  "providerName": "Hilo",
  "gameType": "CASINO",
  "betDetail": "HOLE_3_5",
  "validBet": 100,
  "turnOver": 100,
  "winLose": 200,
  "winLoseWithOutTurnOver": 100,
  "gameResult": "WIN",
  "betTime": 1786615539334,
  "settleTime": 1786615800000,
  "reportDate": "13-08-2026",
  "ip": "127.0.0.1"
}
```

### Reversals

`BET_RETURN` and `CANCEL_SETTLE` both target a bill you already processed.

- **`BET_RETURN`** — the stake goes back to the player. Sent with the bet's `_B` deal id.
- **`CANCEL_SETTLE`** — a settlement is being undone, usually because a round result was
  corrected. The `id` field is **not** generated by us for this action: we echo back the
  transaction id **you** returned when you accepted the original settle. Use it to locate
  the transaction to reverse.

If a corrected result produces a new payout, a fresh `SETTLE` follows the
`CANCEL_SETTLE` with a new `id`.

## Response

Return `code` `0` (also accepted: `200`, `201`) for success. Always include the balance:

```json
{ "code": 0, "balanceBefore": 1000, "balanceAfter": 900 }
```

!!! warning Balance is required on every successful response
This includes responses where you reject the call as a duplicate. Without
`balanceBefore` / `balanceAfter` we cannot show the player their balance.
!!!

On failure, return a non-success `code` **and** an `errorType`:

```json
{ "code": 4000, "errorType": "INSUFFICIENT_BALANCE", "message": "Insufficient balance" }
```

| errorType | Meaning | Mangoplay retries? |
| --- | --- | --- |
| `DUPLICATE_TRANSACTION` | You have already applied this `dealId` | No — see below |
| `INSUFFICIENT_BALANCE` | Player does not have enough credit | Configurable |
| `PLAYER_NOT_FOUND` | Unknown player | Configurable |
| `PLAYER_SUSPENDED` | Player suspended | Configurable |
| `PLAYER_INACTIVE` | Player closed | Configurable |
| `INVALID_SECURITY` | Bad credentials | Configurable |

A timeout, a connection error, or a `5xx` is always retried — we cannot tell from those
whether you applied the transaction.

## Idempotency — the important part

**De-duplicate by `dealId`.**

`dealId` is unique per bet. Every retry of the same logical transaction reuses the same
`dealId` **and** the same `id`, so either field is a safe de-duplication key.

!!! danger Do not de-duplicate by `betId`
`betId` identifies the **round**, not the bet. When a player places several bets in one
round, every one of them carries the same `betId`. De-duplicating on it would silently
reject all bets after the first.
!!!

When you receive a `dealId` you have already applied, return
`errorType: "DUPLICATE_TRANSACTION"` together with the balance from the original
transaction. Do not apply it a second time.

## Retries and timeouts

| Call | Default timeout | Retries |
| --- | --- | --- |
| `BET` | 10 s | up to 5, one second apart |
| `SETTLE`, `BET_RETURN`, `CANCEL_SETTLE` | 5 s | background job, up to 3 |

If your system is consistently slower than these defaults we can raise the timeout for
your agent — tell us your realistic p99 response time for bet and for settle separately.

!!! info Why the timeout matters
If we time out on a call you actually processed, our records and yours disagree: you
debited the player, we recorded the bet as failed. Raising the settle timeout to match
your real response time is the single most effective way to prevent bills stuck on
"awaiting payment".
!!!

## Per-agent options

These are configured by Mangoplay on request, once the behaviour is confirmed with your
team. Every one of them defaults to off, so nothing changes unless you ask.

| Option | What it does | Prerequisite |
| --- | --- | --- |
| Treat duplicates as success | A `DUPLICATE_TRANSACTION` reply closes the bill as paid instead of failing it | You de-duplicate by `dealId` |
| Stop retrying on final errors | Errors such as `INSUFFICIENT_BALANCE` go straight to manual review instead of being retried | Those errors are never transient on your side |
| Settle timeout | Raises the timeout for settlement calls only | — |
| Bet timeout | Raises the timeout for bet calls only. Players wait on this call, so raise it only if you are genuinely slow on bet | — |

## When something goes wrong

Do not raise a ticket for a single stuck bill. Use the
[Reconciliation APIs](https://api-docs.mangoplay.com/) instead:

1. `POST /api/v1/open/seamLess/betInfo` — shows the bet status, the settle status, and
   the last request/response we exchanged with you for that deal.
2. `POST /api/v1/open/seamLess/reSettle` — flips a bet we marked failed to success and
   settles it. Idempotent, and safe to call again. Call it only after the round closed.

Both use your `AgentPrefixId` and `Security` headers and only reach your own deals.
