Receive Tapfeed orders in a POS
Last updated: October 5, 2026
This page is for POS providers and integrators. Tapfeed is a QR table ordering and payment solution: when a restaurant connects its POS, every order is sent to it in real time, as signed JSON, to an HTTPS address you provide.
By default, servers key Tapfeed orders into the POS from the “To enter in the POS” list. The automatic integration described here is offered on request to Premium establishments using Deliverect, an open POS or an integrator: Tapfeed opens it after talking with the restaurant.
1. How it works
The restaurant enters your receiving address in Tapfeed (Management → POS) and gives you the signing key shown on that page. Tapfeed then sends a POST request for each event.
- Answer with a 2xx code within 10 seconds: the send is then considered received.
- With no answer, or a 408, 425, 429 or 5xx code, Tapfeed retries automatically: after 15 s, 1 min, 5 min, 15 min, then further apart, for about 28 hours.
- Other 4xx codes are a final rejection (shown to the restaurant, which can retry).
- Redirects (3xx) are not followed. The address must use HTTPS and be reachable from the Internet.
2. Events
tapfeed.test: sent by the “Send a test” button (no order).order.created: an order goes to the kitchen. If the client paid when ordering, the payment is included.order.paid: the order, already received, has just been paid online (bill paid on the phone).order.cancelled: the order is cancelled.
For a given order, events always arrive in this order: order.paid and order.cancelled are only sent after order.created was received.
3. Headers
Content-Type: application/jsonTapfeed-Event: the event type.Tapfeed-Signature:t=<timestamp>,v1=<signature>, where the signature is the HMAC SHA-256 (hex) oft + "." + bodywith the signing key.Idempotency-Key: the same on every retry of the same send. If you already processed it, just answer 2xx without creating it twice.
4. Verifying the signature
Reject any send with an invalid signature or a timestamp older than 5 minutes.
// Node.js — verify a Tapfeed signature
import crypto from 'node:crypto';
function tapfeedSignatureOk(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
if (!parts.t || !parts.v1) return false;
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false; // 5 minutes
const expected = crypto.createHmac('sha256', secret)
.update(`${parts.t}.${rawBody}`, 'utf8').digest('hex');
return parts.v1.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}
// rawBody : the EXACT body received (before JSON.parse)
// header : “Tapfeed-Signature” header; secret: key shown in Management → POS5. Payload
{
"id": "evt_1842",
"type": "order.created",
"created": 1791403200,
"api_version": "2026-10-01",
"restaurant": { "id": "7c1e…", "name": "Café Boréal", "slug": "cafe-boreal" },
"order": {
"id": "b0f4…", "number": 42,
"table": { "id": "1d2c…", "label": "7" },
"status": "received", "lang": "fr", "pay_mode": "now",
"created_at": "2026-10-06T18:00:00Z", "currency": "CAD",
"lines": [{
"line_id": "9a1b…", "item_id": "3e5f…", "pos_code": "101",
"name": "Poutine", "name_fr": "Poutine", "name_en": "Poutine",
"station": "kitchen", "qty": 2,
"unit_price_cents": 1250, "total_cents": 2500,
"options": [{ "modifier_id": "4c6d…", "pos_code": "M-12", "name": "Grande",
"name_fr": "Grande", "name_en": "Large", "price_cents": 200 }],
"comment": "sans oignon", "happy_hour": false
}],
"subtotal_cents": 2500,
"taxes": [{ "code": "TPS", "rate": 5, "amount_cents": 125 },
{ "code": "TVQ", "rate": 9.975, "amount_cents": 249 }],
"total_cents": 2874,
"payment": { "status": "paid", "provider": "stripe", "reference": "pi_3Q…",
"covers_orders": ["b0f4…"], "amount_cents": 3249, "tip_cents": 375,
"card_brand": "visa", "card_last4": "4242", "paid_at": "2026-10-06T18:00:05Z" },
"missing_codes": 0
}
}- Amounts in cents (integers), in the stated currency. unit_price_cents includes option prices.
- pos_code: the item (or option) code in your POS, entered or imported by the restaurant in Tapfeed. It can be null: then use the name and price. missing_codes tells how many lines have no code.
- station: kitchen or bar. comment: the client’s note, to print on the ticket.
- happy_hour: true when the line is billed at the happy hour price. unit_price_cents is then the reduced price, and pos_code the happy hour item code if one was entered (otherwise the usual code).
- payment: only present when the order is paid online. One payment can settle several orders from the same table (covers_orders): amount_cents and tip_cents are those of the whole payment.
- Fields added later will never remove or change existing ones: ignore those you don’t know.
6. Response
The response body is optional. If you return JSON with pos_ref (or reference, or id), Tapfeed shows it to the restaurant as the order number in your POS.
7. Quebec mandatory billing
Tapfeed is not a sales recording system (SRS): the official bill must be produced by the restaurant’s certified POS, which communicates with Revenu Québec’s MEV-WEB. Orders and payments received from Tapfeed must be recorded and billed by your POS like any other sale.
8. Partnership and contact
Are you a POS provider wanting a direct connector (no address for the restaurant to enter), or a test environment? Write to us: support@tapfeed.com.