Testing Your Integration
Fire a real, fully-signed webhook at your own endpoint whenever you want — without moving any funds. Use it to verify your handler end-to-end before going live, and any time afterwards.
The test event is byte-for-byte a production event — same fields, same units, same signature — with one extra field: test.
Your handler MUST check test and skip all real side effects — crediting a user, marking a withdrawal settled, updating balances. Acknowledge with { "error": 0 } without applying any ledger change. Production events never include this field.
Request
curl -X POST "https://api.<host>/session/<session-uuid>/test-webhook" \
-H "Authorization: Bearer $OPERATOR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{}'<session-uuid>— the session id (sid) you received when you created the session viaPOST /session/depositorPOST /session/withdraw. The session's type decides which event is sent: a deposit session sendsDEPOSIT, a withdraw session sendsWITHDRAW_COMPLETE.- Auth — your
OPERATOR_SECRET_KEY, the same key you create sessions with. Only your own sessions are addressable; anything else returns404. - Destination — your configured
webhookUrl. You cannot point a test at another URL. - Rate limit — 5 requests per minute.
Optional overrides
Send {} to use well-formed defaults derived from the session, or override any of:
| Field | Applies to | Description |
|---|---|---|
amount | both | Decimal string, in your platform fiat (e.g. "500.00"). Defaults to the session's amount, or 100 |
currency | both | Fiat code (e.g. "TRY"). Defaults to your platform currency |
chainId | DEPOSIT | Chain id for the payload. Defaults to 8453 (Base) |
address | both | Token contract for DEPOSIT (omit for a native transfer) / destination address for WITHDRAW_COMPLETE |
-d '{ "amount": "500.00", "currency": "TRY", "chainId": 8453 }'Response
The webhook is delivered synchronously, so you get your own endpoint's result back immediately:
{
"status": "success",
"data": { "delivered": true, "status": 200, "event": "DEPOSIT" }
}| Field | Description |
|---|---|
delivered | Whether your endpoint accepted the event (HTTP 2xx and { "error": 0 } in the body) |
status | The HTTP status your endpoint returned — null when it could not be reached (DNS, connection refused, timeout) |
event | DEPOSIT or WITHDRAW_COMPLETE |
reason | Present only when delivered is false — your endpoint's error message, or the network error |
A rejected or unreachable endpoint is not an error on this call: you still get 200 with delivered: false and the reason, so you can debug from the response.
400 responses mean the test could not be built: no webhookUrl or webhookSecret configured on your operator, or the session has no externalUserId.
What the payload looks like
Identical to the production event documented in Event Types — including units: amount / grossAmount / feeAmount are raw token base units, and amountInDollar / amountInLocalCurrency are decimals. Parse it exactly as you parse a real event.
The synthetic values are chosen so a test is recognisable and repeatable:
test—"true". The one field a production event never has.depositId(DEPOSIT) — a negative number. Real deposits always have a positive id, so a negative value can never collide with one.idempotencyKey(DEPOSIT) —test-<session-uuid>.- Deterministic per session — sending twice for the same session produces the identical
depositId,idempotencyKeyandtransactionHash, so you can exercise your deduplication logic. transactionHash— well-formed but synthetic; it does not exist on-chain.currentRate— always"1", and the local-currency figures equal the USD ones. A test event is not an FX snapshot.- No fee —
feeAmountis0andgrossAmountequalsamount.
Verifying the signature
The hash is computed with your webhook secret exactly like a production event — verify it the same way, with no special-casing for tests. See Signature Verification.
The body is form-urlencoded, so every value arrives as a string: check test === "true", not test === true. Fields that are null at the source arrive as the literal string "null" — see Event Types.
Handler example
app.post('/api/webhook', (req, res) => {
if (!verifySignature(req.body, SECRET_KEY)) {
return res.status(401).json({ error: 1, description: 'invalid signature' });
}
const isTest = req.body.test === 'true';
if (isTest) {
// Parse and validate exactly as you would a real event — but do not touch
// the ledger. Acknowledge so the delivery is reported as successful.
console.log('test webhook received:', req.body.event);
return res.json({ error: 0, description: 'test acknowledged' });
}
// ... real crediting / settlement here
return res.json({ error: 0, description: 'ok' });
});What this does and does not cover
Covered: connectivity and TLS to your endpoint, signature verification, payload parsing, your response contract ({ "error": 0 }), and deduplication on repeat sends.
Not covered: the durable retry path. Production DEPOSIT and WITHDRAW_COMPLETE events are queued and retried up to 5× with backoff before dead-lettering; a test is a single synchronous delivery (with 2 network-level attempts). See Retries & Timeouts.