Webhook payload
{
"event": "payment.confirmed",
"invoiceId": "65f8a1b2c3d4e5f6a7b8c9d0",
"timestamp": "2026-04-10T18:32:14.000Z",
"amount": "25.000000",
"token": "USDT",
"network": "TRON",
"txHash": "abc123โฆ",
"confirmations": 20,
"simulated": false
}
Sent with these headers:
Content-Type: application/json
User-Agent: HelaMesh-Webhooks/1.0
X-HelaMesh-Signature: t=1700000000,v1=<hex>
Field referenceโ
| Field | Type | Notes |
|---|---|---|
event | string | Always "payment.confirmed" for v1 |
invoiceId | string | The invoice ID you stored when creating the invoice |
timestamp | ISO8601 | When HelaMesh enqueued this delivery |
amount | string | Decimal amount, always 6-decimal form for TRON, 18-decimal for BSC |
token | string | "USDT" |
network | string | "TRON" or "BSC" |
txHash | string | On-chain transaction hash. Prefixed with sim_ for simulated payments. |
confirmations | number | Number of confirmations at the moment of webhook enqueue |
simulated | boolean | true when this event was generated by the sandbox (test mode simulate action), never true for live invoices. Route these to staging. |
Respect the simulated flagโ
Every webhook triggered by the sandbox simulate endpoint carries "simulated": true at the top level. If your handler moves real balances, updates accounting, or triggers irreversible fulfillment, branch on this flag and route simulated events to a staging path โ never mirror them into production tables.
app.post('/webhooks/helamesh', async (req, res) => {
// ... verify signature ...
const event = JSON.parse(req.body.toString());
if (event.simulated) {
// Test webhook โ update staging tables, don't touch prod
await stagingDb.orders.update(/* โฆ */);
} else {
// Real payment โ full fulfillment flow
await db.orders.update(/* โฆ */);
await sendConfirmationEmail(/* โฆ */);
}
res.status(200).send('ok');
});
Live invoices can never produce simulated: true โ the simulate endpoint is rejected at the service layer for live clients. You only need to care about this flag if you use the sandbox. See Sandbox (test mode) for the full test-mode flow.
Your handler must be idempotentโ
HelaMesh may deliver the same event twice if your endpoint ack'd 2xx but we didn't see it (network blip). Always check your database for an existing record before applying the payment:
await db.orders.update({
where: {
helamesh_invoice_id: event.invoiceId,
payment_status: { not: 'paid' } // skip if already paid
},
data: { payment_status: 'paid', tx_hash: event.txHash }
});