https://receiptter.eu/api/v1. All bodies are JSON. All amounts are integers in minor units (cents).Authentication
Create a key in the dashboard under Integrations. It is shown once. Send it as a bearer token. Keys can be revoked at any time and are limited to 600 requests per minute.
Authorization: Bearer rk_…
Create a receipt
Call this when the payment is complete. The receipt waits for the customer's tap on that till for the claim window you set (default 3 minutes).
POST /api/v1/receipts
{
"register": "KASA-02",
"external_id": "TX-10045",
"receipt_number": "0000452",
"currency": "EUR",
"total_minor": 1100,
"items": [
{ "name": "Flat white", "qty": 2, "unit_price_minor": 370, "total_minor": 740, "vat_rate": 20 },
{ "name": "Almond croissant", "qty": 1, "unit_price_minor": 360, "total_minor": 360, "vat_rate": 20 }
],
"vat": [{ "rate": 20, "base_minor": 917, "amount_minor": 183 }],
"payment_method": "Card",
"fiscal": { "unp": "DT123456-0001-0000452", "device": "DT123456" }
}
Fields
| Field | Required | Description |
|---|---|---|
register | Yes | The till's POS ID (set in the dashboard) or its internal ID. |
external_id | Yes | Your transaction ID. Unique per merchant; used for idempotency. |
total_minor | Yes | Total in cents. Negative for refunds. |
items[] | Yes | name, qty, unit_price_minor, total_minor; optional discount_minor, vat_rate. |
currency | No | ISO 4217, default EUR. |
vat[] | No | rate, base_minor, amount_minor. |
payment_method | No | A category like Card or Cash. Never send card numbers; digit runs of 6+ are masked. |
fiscal | No | unp, device, qr from your fiscal device. |
receipt_number, issued_at, timezone | No | Shown on the receipt. issued_at is ISO 8601. |
Response
201 Created
{
"id": "rc_9f2c…",
"status": "pending",
"ready_until": 1793960000,
"claim_url": "https://receiptter.eu/c/rc_9f2c….Kq3…"
}
claim_url is a one-time link. Show it as a QR code on your customer display for phones without NFC. Whoever opens it first collects the receipt.
Retries and duplicates
Sending the same external_id again returns 200 with "duplicate": true and the existing receipt. Retry freely on timeouts; you will never create two receipts for one sale.
Get status
GET /api/v1/receipts/{id or external_id}
Status is one of pending, claimed, expired, void. claim_via is tap or qr.
Void or refund
POST /api/v1/receipts/{id or external_id}/void
The receipt page and the authenticity check then show it as cancelled or refunded. For a partial refund, send a new receipt with a negative total.
List tills
GET /api/v1/registers
Webhooks
Receiptter can tell your own system what happens. Add an HTTPS address in the dashboard under Integrations, Webhooks, and choose the events. Every event is one POST with a JSON body. Answer with any 2xx within 5 seconds.
| Event | When |
|---|---|
receipt.created | A sale arrived from the till. |
receipt.collected | The customer collected the receipt. via is tap or qr. |
receipt.cancelled | The receipt was cancelled or refunded. |
feedback.received | A customer left a rating and a comment. |
subscriber.added | Someone joined your newsletter. The consent text they agreed to is included. |
invoice.requested | A company asked for an invoice under its receipt. |
POST https://your-system.example/receiptter
Receiptter-Event: receipt.collected
Receiptter-Delivery: evt_4c1d…
Receiptter-Signature: t=1793960000,v1=5f2a…
{
"id": "evt_4c1d…",
"type": "receipt.collected",
"created_at": 1793960000,
"data": { "id": "rc_9f2c…", "register_id": "reg_71b0…", "total_minor": 1100, "via": "tap", "collected_at": 1793960000 }
}
Check the signature before you trust a request. v1 is the HMAC-SHA256 of "<t>.<body>" with the secret you were shown when you added the address.
const [t, v1] = header.split(',').map(p => p.split('=')[1]);
const expected = crypto.createHmac('sha256', secret).update(t + '.' + rawBody).digest('hex');
const ok = v1.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
&& Math.abs(Date.now() / 1000 - Number(t)) < 300;
If your side does not answer with 2xx, the event is sent again after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours, with the same id, so you can drop repeats. After 20 failed deliveries in a row the address is switched off until you switch it on again in the dashboard.
Errors
| Status | Meaning |
|---|---|
| 400 | A field is missing or invalid. The error text names it. |
| 401 | Missing, wrong or revoked API key. |
| 402 | The plan's receipts for this month are used up. New receipts pause until the 1st. |
| 403 | The merchant account is suspended. |
| 404 | Unknown till or receipt for this key. |
| 409 | The receipt is archived and can no longer be cancelled. |
| 429 | Rate limit. Retry after a short pause. |