Skip to main content

Webhook payload

POST your-webhook-url
{
"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โ€‹

FieldTypeNotes
eventstringAlways "payment.confirmed" for v1
invoiceIdstringThe invoice ID you stored when creating the invoice
timestampISO8601When HelaMesh enqueued this delivery
amountstringDecimal amount, always 6-decimal form for TRON, 18-decimal for BSC
tokenstring"USDT"
networkstring"TRON" or "BSC"
txHashstringOn-chain transaction hash. Prefixed with sim_ for simulated payments.
confirmationsnumberNumber of confirmations at the moment of webhook enqueue
simulatedbooleantrue 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 }
});