Skip to content

Bet Data Push Callback ​

VCT Platform batch-pushes bet, settlement, and correction transactions to the Agent (Operator).

Note

This API is called by the VCT Platform to the Agent (Operator) to push or batch-push wager logs and report data.

NOTE (Important for Transfer Wallet mode): In Seamless (real-time wallet) mode, this API is the only channel for the Agent to process game balance changes. Bet placements (BET), settlements (SETTLED), bonuses (BONUS), and cancellations/rollbacks (CANCEL/ROLLBACK/REFUND/VOID) are pushed here. The Agent must apply idempotent real-time debits, credits, or reversals to the player's master wallet according to status.

Environment URLs ​

EnvironmentBase URL
Productionhttps://hub-panel-api.v7stech.com
Staginghttps://hub-panel-api-dev.v7stech.com

Endpoint ​

PropertyValue
PathPOST /api/v1/callback/bet-push
CallerVCT Platform → Agent (Operator)

Authentication and Signature ​

See Authentication for the common Header Token rule. Because this endpoint contains batched wagers, its Body sign is HMAC-SHA256(agent_api_key + "." + timestamp, agent_api_secret); the Header and Body timestamps must match exactly.

Request Parameters (from VCT) ​

ParameterTypeRequiredDescription
operator_codestringYesOperator code
wagersarrayYesWager list
wagers[].usernamestringYesRaw player username (without VCT platform agent prefix)
wagers[].currencystringYesCurrency code (e.g., MYR, VND, USD, used to locate the player's currency-specific wallet)
wagers[].session_idstringYesVCT session ID; use it to associate every wager with the matching game launch and offline reclaim
wagers[].bet_idstringYesBet ID
wagers[].brand_guidstringYesStable Brand ID, matching /papi/v1/game/list and /papi/v1/game/providers; use for Brand relationships
wagers[].brand_codestringYesCurrent t_brand.brand_code; not a globally unique key
wagers[].brand_namestringYesCurrent t_brand.brand_name, for display
wagers[].game_codestringYesGame code
wagers[].game_typestringYesProvider-native type retained for compatibility; not the commission genre
wagers[].game_genreintegerYesNumeric catalogue genre; 0 when the game cannot be identified uniquely
wagers[].game_genre_namestringYes2 Slot, 3 Fish, 4 Sport, 5 Card, 6 Live, 7 Other; empty for 0
wagers[].game_namestringYesGame name
wagers[].bet_amountdecimalYesBet debit included in this transaction
wagers[].valid_bet_amountdecimalYesValid bet amount
wagers[].prize_amountdecimalYesPayout, bonus, or refund credit included in this transaction
wagers[].win_lossdecimalYesNet amount for this transaction, normally prize_amount - bet_amount; it may not be the final round P/L
wagers[].statusstringYesWager status/action: BET / SETTLED / CANCEL / VOID / ROLLBACK / REFUND / BONUS
wagers[].bet_timestringYesBet time
wagers[].settle_timestringYesSettlement time
wagers[].round_idstringNoUnified round/game ID. VCT maps provider-specific identifiers to this field so operators can associate BET, SETTLED, and corrections for the same player, currency, and session. Do not use round_id alone as the correlation key.
wagers[].original_transaction_idstringNoOriginal settlement/bet transaction ID; recommended for status=BONUS or correction records (e.g., CANCEL)
signstringYesSignature
timestamplongYesTimestamp

Brand Identity ​

Each wager includes brand_guid, brand_code, and brand_name from the same valid t_brand record. Provider and product data locate the linked Brand GUID; product codes and names are not global identity keys. Distinct GUIDs must remain separate even when codes or names match. Changing a Brand code or name preserves its GUID; deleting and recreating a Brand changes the GUID.

A product may identify a Brand even without a specific game/table, so game_code and game_name may be empty. Missing, deleted, ambiguous, or wrongly owned Brand mappings are not sent as guessed identities: Hub records a delivery failure for correction. Existing delivered wagers are not backfilled or resent automatically. This addition requires the updated Hub backend to be deployed.

game_genre comes from catalogue games matching Platform, Product, and Game Code; a product-only match is classified only if every matching game has the same genre. Mixed or unmatched products and unclassified games return 0 and an empty name. Never infer it from provider-native game_type; hold genre-0 wagers for reconciliation until the settlement policy is agreed.

/game/rates accepts exact brand_guid filters. Query with the wager GUID and original business date in as_of_date; the legacy brand request field searches current master Code or Name and is not unique.

Response Fields ​

FieldTypeDescription
receivedintNumber of records received
statusstringProcessing status

Amounts and Player Win/Loss ​

bet_amount is the bet debit included in the current record. prize_amount is the payout, bonus, or refund credit included in the current record.

For normal betting and settlement records:

text
Net amount for this record = prize_amount - bet_amount

A positive value increases the player's balance, a negative value decreases it, and zero means there is no net balance change in this record. To calculate the final P/L for a round, aggregate normal records for the same player, currency, session_id, and round_id:

text
Round player P/L = sum(prize_amount) - sum(bet_amount)

BET and SETTLED as separate records ​

Some providers send the debit and payout separately:

statusbet_amountprize_amountNet amount
BET1000-100
SETTLED0200+200

For the same round, the player's P/L is 200 - 100 = +100. Do not treat the SETTLED row by itself as the final round P/L.

Bet and payout combined in one SETTLED record ​

Some providers send one SETTLED record containing both amounts:

statusbet_amountprize_amountRound player P/L
SETTLED100200+100

In this format, calculate prize_amount - bet_amount directly. Providers may split rows differently, but the meanings of the two amount fields remain the same.

Status and Balance-Processing Rules ​

statusMeaningCommon amount shapeAgent processing
BETBet debitbet_amount > 0, prize_amount = 0Debit bet_amount
SETTLEDNormal settlementMay contain only prize_amount, or both amountsDebit the included bet_amount and credit prize_amount; if both are present, process them atomically
BONUSExtra bonus or independent payoutUsually bet_amount = 0, only prize_amountCredit prize_amount and classify it separately
REFUNDRefundUsually only prize_amountCredit the refund and mark the original transaction as refunded
CANCEL / ROLLBACK / VOIDCancellation, rollback, or voidProvider-dependent; both amounts may be zeroIdempotently reverse the original transaction using original_transaction_id

action is the internal Hub-System name; in this callback, the Agent processes the same meaning through wagers[].status.

Correction records ​

CANCEL, ROLLBACK, VOID, and REFUND are not new normal wins or losses. The Agent must locate the original transaction with original_transaction_id, check whether it was already reversed, and apply the correction exactly once.

Some providers include the original bet in bet_amount as a reference on a correction record. This does not mean the Agent should debit it again. Money returned to the player is represented by prize_amount. If both amounts are zero, the correction changes reporting state but not the balance. Do not use prize_amount - bet_amount alone to determine the final P/L of a correction.

Example Request ​

Settled Wager SETTLED ​

json
{
  "operator_code": "AG",
  "wagers": [
    {
      "username": "player001",
      "currency": "MYR",
      "session_id": "vct-session-uuid-001",
      "bet_id": "settle-20260706-0001",
      "brand_guid": "30ff77f5-0616-4ecc-a8a3-80e35fcaaf3e",
      "brand_code": "1007",
      "brand_name": "PG Soft",
      "game_code": "PG-SLOT-001",
      "game_type": "SLOT",
      "game_genre": 2,
      "game_genre_name": "Slot",
      "game_name": "Fortune Tiger",
      "bet_amount": 100.0,
      "valid_bet_amount": 100.0,
      "prize_amount": 200.0,
      "win_loss": 100.0,
      "status": "SETTLED",
      "bet_time": "2026-07-06T10:00:00+08:00",
      "settle_time": "2026-07-06T10:01:00+08:00",
      "round_id": "round-10001"
    }
  ],
  "sign": "demo-sign",
  "timestamp": 1783325100
}

Extra Bonus Record BONUS ​

json
{
  "operator_code": "AG",
  "wagers": [
    {
      "username": "player001",
      "currency": "MYR",
      "session_id": "vct-session-uuid-001",
      "bet_id": "bonus-20260706-0001",
      "brand_guid": "30ff77f5-0616-4ecc-a8a3-80e35fcaaf3e",
      "brand_code": "1007",
      "brand_name": "PG Soft",
      "game_code": "PG-SLOT-001",
      "game_type": "SLOT",
      "game_genre": 2,
      "game_genre_name": "Slot",
      "game_name": "Fortune Tiger",
      "bet_amount": 0.0,
      "valid_bet_amount": 0.0,
      "prize_amount": 30.0,
      "win_loss": 30.0,
      "status": "BONUS",
      "bet_time": "2026-07-06T10:01:00+08:00",
      "settle_time": "2026-07-06T10:01:10+08:00",
      "round_id": "round-10001",
      "original_transaction_id": "settle-20260706-0001"
    }
  ],
  "sign": "demo-sign",
  "timestamp": 1783325160
}

Example Response ​

json
{
  "received": 1,
  "status": "success"
}
```## Push Logic & Processing Recommendations

### 1. Real-Time Wallet Processing (CRITICAL)
* **Apply Balance Changes**: Process `BET` as a debit of `bet_amount`. For `SETTLED`, debit any included `bet_amount` and credit `prize_amount` atomically; when `bet_amount` is zero, only credit `prize_amount`. Credit `BONUS` separately. `CANCEL` / `ROLLBACK` / `REFUND` / `VOID` must idempotently correct the original transaction identified by `original_transaction_id`.
* `/offline` closes the session and reconciles the final balance. It must not apply a second debit or credit for transactions already processed through `/bet-push`.

### 2. Idempotency Check
* Use a unique transaction key (at minimum `wagers[].bet_id`, combined with `status` and `original_transaction_id` where applicable) to prevent duplicate debits, credits, or reversals caused by retries.

### 3. Wager Status and Correction Flow
* **Normal Statuses**: `BET` (raw bet placement), `SETTLED` (normal settled wager), `BONUS` (extra payout or independent win).
* **Correction/Rollback Statuses**: `CANCEL`, `ROLLBACK`, `REFUND`, `VOID`.
* **Correction Flow Handling**: When the wager status is a rollback/correction status, VCT provides `original_transaction_id`. The Agent must locate the original wallet transaction, reverse or refund it exactly once, and update its reporting state. A `bet_amount` included for reference must not cause a second debit.

### 4. Automatic Retry Mechanism and Response Codes
* **Successful Processing**: Return a `HTTP 200` status code with `{"received": 1, "status": "success"}`. VCT's queue worker will mark this task as successfully delivered.
* **Retry Guarantee**: If the Agent's database is busy or has transient errors, return `HTTP 5xx` or let the request timeout. VCT's delivery queue will **automatically retry pushing the log** over subsequent intervals to guarantee zero log loss.

## Provider Source Mapping

| Provider | Callback Path | Request Format |
|----------|--------------|----------------|
| BG | — | Push not supported |
| GSC | `seamless/pushbetdata` | JSON, wagers array |
| SA | — | Push not supported (use query API to pull) |
| WM | `webhook` (cmd=SendMemberReport) | form-data, result array |

## AI Implementation Prompt (Copy Ready)

```text
Implement the Agent endpoint POST /api/v1/callback/bet-push. This is the Seamless-wallet source of truth for debits, payouts, bonuses, and corrections.

Requirements:
1. Verify the three-part Authorization Token, require Header timestamp == Body timestamp, and verify Body sign as lowercase hex HMAC-SHA256(agent_api_key + "." + timestamp, agent_api_secret) using constant-time comparison.
2. Verify operator_code belongs to this Agent and validate every wager's username, currency, session_id, bet_id, status, and amounts.
3. Use decimal/fixed-point money types, never float. Process each batch in a database transaction and return success only after commit.
4. Enforce a stable idempotency key including Agent, bet_id, status, and original_transaction_id as appropriate.
5. BET debits bet_amount. SETTLED debits its bet_amount and credits prize_amount. BONUS credits prize_amount. BET/SETTLED may be split or combined and may arrive out of order.
6. CANCEL/ROLLBACK/REFUND/VOID must locate the original transaction via original_transaction_id and reverse it exactly once; a reference bet_amount must not cause another debit.
7. Store session_id, round_id, brand_guid, brand_code, brand_name, game fields, timestamps, and raw payload. Relate wagers to the catalogue using brand_guid, never brand_code/name alone. Keep equal names/codes with different GUIDs separate. Preserve historical records lacking GUID and mark Brand attribution unresolved; never invent a GUID. Preserve wallet handling and query rates using brand_guid plus the original business date in as_of_date. Do not use round_id alone as a unique key.
7a. Use numeric game_genre (2 Slot, 3 Fish, 4 Sport, 5 Card, 6 Live, 7 Other) for commission and game_genre_name only for display. The old game_type is provider-native. Hold genre-0 wagers for reconciliation; do not guess a type or silently charge commission.
8. Return HTTP 200 with {"received": committed_count, "status":"success"} only when all required work commits. Return 5xx for transient failures so VCT retries.
9. Offline reclaimed_amount must not change wallet funds already processed by bet-push.
10. Handle duplicates, out-of-order, and late records idempotently. Redact credentials/signatures from logs.
11. Test BET, split/combined SETTLED, BONUS, all corrections, batch atomicity, duplicate/out-of-order/late delivery, precision, invalid signatures, database failures, and distinct Brand GUIDs sharing the same code/name.

Reuse the existing wallet ledger, transactions, unique constraints, and callback authentication; do not create a second balance source of truth.

VCT Gaming Integration Platform