Webhooks
Triple-A sends an HTTP POST with Content-Type: application/json to your notify_url each time a payment status changes. Respond with any 2xx status to confirm that you received it.
Retries
If you return anything other than a 2xx status, or your URL is unreachable, Triple-A retries up to five times after the first attempt. The table below shows how long Triple-A waits before each retry.
| Retry | Wait after the previous attempt |
|---|---|
| 1st | 30 seconds |
| 2nd | 60 seconds |
| 3rd | 5 minutes |
| 4th | 10 minutes |
| 5th | 10 minutes |
Because of retries, the same notification can arrive more than once, so make your handler safe to run twice for the same payment. If all retries fail, contact [email protected] to have the webhook triggered again.
Fields
The table below lists the fields you need to handle a payment notification. The payment webhook callback on Make a payment request has the full list.
| Field | Use it for |
|---|---|
event | The notification type. It’s payment for payment notifications. |
payment_reference | Matching the notification to the payment you created. |
status | The payment status. See Payment statuses. |
order_id | Your own order ID, if you set one. |
crypto_currency, crypto_amount | What the customer paid. See Coins and networks. |
payment_currency, payment_amount | The guaranteed amount in your preferred currency. |
api_id | The account that was used. A sandbox API ID means a test payment. |
Triple-A also sends transaction_fee_amount, vat_amount, and cart. The txs field is for Triple-A’s debugging, so you can ignore it. To choose which amount to reconcile on, see Your first payment.
Verify the signature
Every webhook carries a Triplea-Signature header. Its value has a timestamp and a signature in the following format.
t=<unix-timestamp>,v1=<hex-encoded-signature>To verify a webhook, follow these steps.
t and v1 from the header.<t>.<raw request body>.notify_secret as the key, and hex-encode it.v1.t is within 300 seconds of the current time.Verify against the exact bytes you received, before any JSON parsing. Parsing and serializing again can change the formatting and break the signature.
The following Node.js example verifies a webhook with a constant-time comparison. The highlighted lines read the raw body, sign the raw bytes, and compare the signatures in constant time.
const crypto = require('crypto');
const express = require('express');
const app = express();
const SECRET = process.env.TRIPLEA_NOTIFY_SECRET;
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const header = req.headers['triplea-signature'] || '';
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const expected = crypto
.createHmac('sha256', SECRET)
.update(`${parts.t}.${req.body}`) // req.body is a Buffer of the raw bytes
.digest('hex');
const a = Buffer.from(parts.v1 || '', 'hex');
const b = Buffer.from(expected, 'hex');
const valid = a.length === b.length && crypto.timingSafeEqual(a, b);
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300;
if (!valid || !fresh) return res.status(400).end();
const payment = JSON.parse(req.body);
// Look up the order by payment.payment_reference or payment.order_id,
// then act on payment.status.
res.status(200).end();
});
app.listen(3000);The example is adapted from the Node.js sample on developers.triple-a.io, which also provides a sample body, secret, and header for testing your verification code. Comment out the timestamp check when you use them, because the sample is older than 5 minutes.
Related content
These pages cover related topics.
- Stablecoin checkout shows where webhooks fit in a full checkout
- Error codes lists the errors a payment request can return