Webhook
The webhook notifies your backend, via a signed POST, whenever a TRP transaction changes status — so you don’t have to keep polling for the status.
Configuration
Section titled “Configuration”There are two levels, which can be combined.
In the console (account level)
Section titled “In the console (account level)”Set a URL that receives notifications for all of your transactions. Only the partner owner can configure it.
The easiest way is the Webhook screen in the console (side menu): paste the URL, generate the secret, and enable it — no code. For automation, the same endpoints are available via the API (authenticate with the console session token):
curl -X PUT https://api.tonramp.io/v1/partner/webhook \ -H "Authorization: Bearer <session-token>" \ -H "Content-Type: application/json" \ -d '{"url": "https://your-store.com/webhooks/tonramp"}'Response — the secret is shown only once (store it securely; it’s what you use to validate the signature):
{ "webhook_url": "https://your-store.com/webhooks/tonramp", "webhook_enabled": true, "secret": "<signing-secret>" }GET /v1/partner/webhook retrieves the current configuration (without the secret); DELETE /v1/partner/webhook disables it.
Per order (via API)
Section titled “Per order (via API)”For a callback specific to a single transaction, send callback_url in the POST /v1/wallet/trp/generate. It takes priority over the account webhook for that tx_id. The URL does not go in the payload — it is stored on the server.
curl -X POST https://api.tonramp.io/v1/wallet/trp/generate \ -H "Authorization: Bearer tonr_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "wallet": "UQBJ6gU8gh_jRrzYDlfw9cpCwHaSn2mrK4O-1h8CDENehGYJ", "merchant": "store123", "amount": 100.00, "currency": "USDT", "tx_id": "order-001", "callback_url": "https://your-store.com/webhooks/order-001" }'Test the integration
Section titled “Test the integration”After configuring, fire a test webhook to verify that your endpoint receives the POST and validates the signature — without depending on a real transaction.
- In the console: Webhook screen → Test webhook button. Shows, in real time, the HTTP status your endpoint returned.
- Via API:
POST /v1/partner/webhook/test(owner only, console session token).
curl -X POST https://api.tonramp.io/v1/partner/webhook/test \ -H "Authorization: Bearer <session-token>"The response carries the delivery result — the HTTP status your endpoint returned:
{ "delivered": true, "status_code": 200, "url": "https://your-store.com/webhooks/tonramp", "error": null }The body sent is a sample signed with event: "trp.webhook.test" and test: true — it does not correspond to any real order.
Events
Section titled “Events”The POST is sent on these status changes:
| status | meaning |
|---|---|
paid | PIX received, processing the USDT delivery |
completed | USDT delivered, transaction concluded |
error | processing failure |
expired | expired without payment |
cancelled | cancelled |
Notification body
Section titled “Notification body”Content-Type: application/json:
{ "event": "trp.transaction.status", "tx_id": "order-001", "order_id": "topup_order-00_a1b2", "status": "completed", "amount_usdt": 100.0, "amount_brl": 567.63, "partner_id": 7, "attempt": 1}tx_id— thetx_idfrom the TRP payload (field 05), to match with your order.status— see the events table above.attempt— the delivery attempt number (increments on retries).
Signature
Section titled “Signature”Each request carries the headers:
X-TonRamp-Signature: sha256=<hmac_sha256(secret, raw_body)>X-TonRamp-Event: trp.transaction.statusCompute the HMAC-SHA256 of the raw body received (do not reserialize the JSON) with your secret and compare in constant time.
import hmacimport hashlib
def verify(secret: str, body: bytes, header: str) -> bool: expected = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, header)import crypto from "node:crypto";
function verify(secret, body, header) { const expected = "sha256=" + crypto.createHmac("sha256", secret).update(body).digest("hex"); return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header));}Respond 2xx to confirm receipt. On error, timeout, or status >= 300, the delivery is retried with backoff (up to 5 attempts). Since there may be a retry, handle the notification idempotently (for example, by deduplicating on tx_id + status).