API delivery: deliver from your server
Tocler calls your https endpoint after each payment; you answer with the items or a message.
When a buyer pays for an API delivery product, Tocler sends one signed POST per order line to your endpoint. Your answer is what the buyer receives. API version: 2026-10-30 (it changes only when a field is removed or renamed).
Add the endpoint
Dashboard → Delivery → API delivery → Endpoints → Add. Give a public
https://address. Copy the secret (whsec_…) — it is shown once.Check the signature
Answer
Return
200with JSON: the items (exactly the quantity) and / or a message.Test
Press Test on the endpoint or on a product: Tocler sends a
testrequest with the product’s real ids and sample answers, and shows what your server answered.
POST /your/endpoint HTTP/1.1
Content-Type: application/json
User-Agent: Tocler-API-Delivery/1
x-tocler-event: order.paid
x-tocler-timestamp: 1793650000
x-tocler-signature: v1=5f0c…
x-tocler-delivery: TRX-8F2K1.0{
"id": "TRX-8F2K1.0",
"event": "order.paid",
"apiVersion": "2026-10-30",
"createdAt": "2026-11-02T10:00:00.000Z",
"attempt": 1,
"store": {
"id": "665f1c2e9b1e8a0012345678",
"name": "Test Store",
"url": "https://teststore.tocler.com"
},
"order": {
"id": "TRX-8F2K1",
"createdAt": "2026-11-02T09:59:41.000Z",
"currency": "EUR",
"total": "19.99",
"paidWith": "card"
},
"line": {
"index": 0,
"quantity": 2,
"unitPrice": "9.99",
"product": {
"id": "665f1c2e9b1e8a00aaaaaaaa",
"name": "Game top-up",
"sku": "TOPUP"
},
"variant": {
"id": "665f1c2e9b1e8a00bbbbbbbb",
"name": "500 gems",
"sku": "TOPUP-500"
}
},
"fields": {
"player_id": "88231",
"server": "EU"
},
"fieldList": [
{
"id": "665f1c2e9b1e8a00cccccccc",
"key": "player_id",
"label": "Player ID",
"value": "88231"
},
{
"id": "665f1c2e9b1e8a00dddddddd",
"key": "server",
"label": "Server",
"value": "EU"
}
],
"buyer": {
"email": "buyer@example.com"
}
} | Field | Meaning |
|---|---|
| id | Stable id of this delivery (order id + line). The same on every retry — use it to never deliver twice. |
| event | order.paid, or test from the Test button (then test: true). |
| attempt | 1 on the first try, higher on retries. |
| order.total, line.unitPrice | Decimal text in the order’s currency, e.g. "19.99" — never a float. |
| order.paidWith | card, crypto, store_credit, provider, manual. |
| line.quantity | How many items you must return. |
| fields | The buyer’s answers by their key (questions without a key are only in fieldList). Secret answers are sent in clear only to your signed endpoint. |
| buyer.email | Only when you switched on “Send the buyer’s email” for the endpoint. |
{ "items": ["GEMS-7Q2K-91XA", "GEMS-PL0C-55ZT"] }{ "items": [ { "Login": "player881", "Password": "s3cret!" } ], "message": "Change the password after your first login." }{ "message": "Your 500 gems were added to player 88231." } | Rule | Value |
|---|---|
| Items | Exactly line.quantity; each text up to 4 KB, or an object of up to 8 labelled values (shown like an account; labels containing pass, pin, secret, token or code are masked for staff). |
| Message | Up to 2,000 characters; the buyer sees it on the order page. |
| Time | Answer within 10 seconds. |
| Size | Answer body up to 64 KB. |
| Address | Public https only; no redirects; private and local addresses are refused. |
| Retries | After 1, 5, 15 and 30 minutes (5 tries). Then the line waits for the owner’s Try again, and the owner is emailed. |
const express = require('express');
const crypto = require('crypto');
const app = express();
const done = new Map(); // use your database in production
app.post('/tocler/deliver', express.raw({ type: 'application/json', limit: '64kb' }), async (req, res) => {
const raw = req.body.toString('utf8');
const ts = req.get('x-tocler-timestamp');
const expected = 'v1=' + crypto.createHmac('sha256', process.env.TOCLER_ENDPOINT_SECRET).update(`${ts}.${raw}`).digest('hex');
const sig = Buffer.from(req.get('x-tocler-signature') || '');
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300 || sig.length !== expected.length || !crypto.timingSafeEqual(sig, Buffer.from(expected))) {
return res.status(401).end();
}
const body = JSON.parse(raw);
if (done.has(body.id)) return res.json(done.get(body.id)); // a retry: same answer
const items = [];
for (let i = 0; i < body.line.quantity; i += 1) items.push(await makeCode(body.line.variant.sku, body.fields));
const answer = { items };
if (!body.test) done.set(body.id, answer);
res.json(answer);
}); Was this page helpful?