Flavoso APIWebhooks und EreignisseWebhooks
Webhooks
Flavoso meldet jede Änderung sofort per signiertem POST an deinen Server: einrichten, empfangen, quittieren, Wiederholungen und Protokoll.
So funktionieren Webhooks
Ändert sich etwas in einem Betrieb, der deine App verbunden hat, entsteht ein Ereignis, und Flavoso schickt es per POST an die Webhook-Adresse deiner App. Deine App muss nicht ständig nachfragen: eine neue Bestellung ist in der Regel nach wenigen Sekunden da.
Änderung im Betrieb
Dashboard, Küche, Handy-App, Bondrucker oder API
Ereignis
Umschlag mit id, type, data; beim ersten Versand eingefroren
Zustellung
POST an deine Adresse, signiert mit Flavoso-Signature
Zugestellt
dein Server antwortet mit 2xx in 10 Sekunden
Sonst: neuer Versuch 8 Versuche über knapp zwei Tage
- 1sofort
- 21 Min.
- 35 Min.
- 430 Min.
- 52 Std.
- 66 Std.
- 712 Std.
- 824 Std.
Scheitern mindestens 10 Versuche am Stück über 24 Stunden, wird die Adresse abgeschaltet, und du bekommst eine E-Mail.
Was verloren ging, holst du mit GET /events nach, 30 Tage zurück.
Eine App hat genau einen Webhook. An ihn gehen die Ereignisse aller Betriebe, die deine App verbunden haben, soweit die Verbindung das passende Leserecht hat (siehe Rechte und Ereignisse). Dazu kommen die Ereignisse deiner Verbindungen selbst (app.*).
Einrichten
- 1
Adresse eintragen
Eine
https-Adresse, aus dem Internet erreichbar, höchstens 500 Zeichen. Adressen in privaten Netzen nimmt Flavoso nicht an.Konsole › App › Webhooks
- 2
Ereignisse wählen
Nur was deine App braucht. Eine Kasse braucht
order.*, oftmenu.*undstore.*, immerapp.disconnected. - 3
Signaturgeheimnis kopieren
Es beginnt mit
whsec_. Damit prüft dein Server jede Zustellung. Du kannst es in der Konsole unter Webhooks jederzeit wieder anzeigen lassen oder erneuern. - 4
Test-Ereignis senden
Prüft den ganzen Weg: Adresse, Signatur, Antwort. Das Ergebnis steht bei den Zustellungen darunter.
„Neues Signaturgeheimnis erzeugen“ ersetzt das Geheimnis; 24 Stunden lang signiert Flavoso dann mit beiden. Einen Webhook kannst du ausschalten und wieder einschalten, oder löschen: dann sind Adresse, Geheimnis und alle Zustellungen samt Protokoll weg. Die Ereignisse selbst bleiben 30 Tage über GET /events abrufbar.
Der Umschlag
Jede Zustellung ist ein JSON-Umschlag. Er ist für Webhook und GET /events derselbe:
| Feld | Bedeutung |
|---|---|
id | Kennung des Ereignisses (evt_…). Daran erkennst du Doppelte. |
object | Immer event. |
type | Die Art, etwa order.created. Siehe Ereignisarten. |
created_at | Wann die Änderung passiert ist. |
api_version | Das Versionsdatum, wie in Flavoso-Api-Version. |
livemode | false beim Test-Betrieb. |
tenant_id | Der Betrieb. |
connection_id | Die Verbindung deiner App zu diesem Betrieb. Damit findest du die passenden Tokens. |
data | Der Inhalt: data.object ist das Objekt in derselben Form, die die Routen liefern, dazu je nach Art previous_status, changed oder previous. |
{
"id": "evt_2bf5a9b06b75442a9244e884a1acfc57",
"object": "event",
"type": "order.created",
"created_at": "2026-10-04T17:45:10.410Z",
"api_version": "2026-10-08",
"livemode": true,
"tenant_id": "7d0e5b8a-3c41-4f2e-9a6b-1e2d3c4b5a69",
"connection_id": "4b3f7c1e-9a2d-4e8b-b5f6-0c1d2e3f4a5b",
"data": {
"object": {
"object": "order",
"id": "cce01f52-5188-410f-9659-6ce26ba9b784",
"code": "3271CD",
"type": "delivery",
"status": "pending",
"source": "storefront",
"created_at": "2026-10-04T17:44:59.942Z",
"updated_at": "2026-10-04T17:45:10.118Z",
"requested_for": null,
"currency": "EUR",
"amounts": {
"subtotal": 2490,
"delivery_fee": 250,
"service_fee": 0,
"discount": 0,
"tip": 200,
"total": 2940
},
"tax": {
"included": true,
"lines": [
{
"rate_bps": 700,
"gross": 2421,
"net": 2263,
"tax": 158
},
{
"rate_bps": 1900,
"gross": 319,
"net": 268,
"tax": 51
}
]
},
"payment": {
"method": "online",
"status": "paid",
"paid_at": "2026-10-04T17:45:10.000Z"
},
"customer": {
"name": "Alex",
"phone": "+49 151 00000000"
},
"delivery": {
"address": "Musterstraße 1",
"postal_code": "10115",
"city": "Berlin"
},
"table": null,
"note": "Please ring twice",
"external_id": null,
"acknowledged_at": null,
"items": [
{
"id": "e9c0ce66-7f7f-42cf-a4bf-7d66f87768c1",
"product_id": "303b87c9-58a3-4644-9a8b-2841cc1f89d6",
"name": "Pizza Margherita",
"quantity": 2,
"unit_price": 1100,
"line_total": 2200,
"tax_rate_bps": 700,
"options": [
{
"id": "1f2e3d4c-5b6a-4798-8a7b-6c5d4e3f2a1b",
"name": "Extra cheese",
"price_delta": 150,
"group_id": "8e7d6c5b-4a39-4281-b0f9-e8d7c6b5a4f3",
"group_name": "Extras"
}
]
},
{
"id": "0d1e2f3a-4b5c-4d6e-8f70-81a2b3c4d5e6",
"product_id": "6a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d",
"name": "Cola 0,33 l",
"quantity": 1,
"unit_price": 290,
"line_total": 290,
"tax_rate_bps": 1900,
"options": []
}
]
}
}
}Dazu kommen diese Kopfzeilen:
| Kopf | Bedeutung |
|---|---|
Flavoso-Signature | Die Signatur, siehe Signaturen prüfen. |
Flavoso-Event-Id | Dasselbe wie id im Umschlag. |
Flavoso-Event-Type | Dasselbe wie type. |
Flavoso-Delivery-Attempt | Die wievielte Sendung dieser Zustellung das ist, ab 1. Gezählt wird jede, automatisch oder von Hand erneut gesendet. |
Content-Type | application/json |
User-Agent | Flavoso-Webhooks/1.0 (+https://docs.flavoso.com/en/api), damit du Zustellungen in deinen Protokollen erkennst. |
POST /flavoso/webhook HTTP/1.1
Host: pos.example.com
Content-Type: application/json
User-Agent: Flavoso-Webhooks/1.0 (+https://docs.flavoso.com/en/api)
Flavoso-Signature: t=1759598710,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
Flavoso-Event-Id: evt_2bf5a9b06b75442a9244e884a1acfc57
Flavoso-Event-Type: order.created
Flavoso-Delivery-Attempt: 1
{"id":"evt_2bf5a9b06b75442a9244e884a1acfc57","object":"event","type":"order.created",...}Empfangen und quittieren
Eine Zustellung gilt als angekommen, wenn dein Server innerhalb von 10 Sekunden mit einem beliebigen 2xx antwortet. Was im Rumpf deiner Antwort steht, ist egal.
- Erst die Signatur prüfen, dann antworten, dann arbeiten: eine lange Verarbeitung vor der Antwort läuft in die Zeitgrenze, und Flavoso schickt dieselbe Zustellung noch einmal.
- Eine Weiterleitung (3xx) gilt als Fehlschlag. Trag die endgültige Adresse ein.
- Merk dir die
idjedes verarbeiteten Ereignisses und überspring Doppelte.
import express from "express";
import { verifyFlavosoSignature } from "./flavoso-signature.js"; // see "Verifying signatures"
const app = express();
const SECRET = process.env.FLAVOSO_WEBHOOK_SECRET; // whsec_...
// express.raw: the body stays exactly as it arrived, which the signature needs
app.post("/flavoso/webhook", express.raw({ type: "application/json" }), async (req, res) => {
const raw = req.body.toString("utf8");
if (!verifyFlavosoSignature(raw, req.get("Flavoso-Signature") ?? "", SECRET)) {
return res.status(400).send("invalid signature");
}
const event = JSON.parse(raw);
// Answer within 10 seconds, then work. Duplicates are possible: the event id decides.
res.sendStatus(200);
if (await alreadyHandled(event.id)) return;
await queue.add(event); // your job queue: book the order, update the menu, ...
});
app.listen(3000);Wiederholungen
Alles außer 2xx gilt als Fehlschlag: ein Fehlercode, eine Zeitüberschreitung, eine Weiterleitung, ein nicht erreichbarer Server. Dann versucht Flavoso es erneut, 8 Versuche insgesamt über knapp zwei Tage. Eine Kasse, die über Nacht aus ist, bekommt am Morgen noch alles.
| Versuch | Abstand zum vorigen |
|---|---|
| 1 | sofort |
| 2 | 1 Min. |
| 3 | 5 Min. |
| 4 | 30 Min. |
| 5 | 2 Std. |
| 6 | 6 Std. |
| 7 | 12 Std. |
| 8 | 24 Std. |
Die Abstände zählen jeweils ab dem vorigen Versuch. Flavoso-Delivery-Attempt zählt jede Sendung mit, auch die von Hand. Jeder Versuch schickt denselben Umschlag, Byte für Byte, mit frischer Signatur.
Abschalten bei Dauerfehlern
Scheitern mindestens 10 Versuche am Stück und kommt 24 Stunden lang nichts an, schaltet Flavoso den Webhook ab und schreibt dir eine E-Mail. Offene Zustellungen werden dann übersprungen.
Nach der Reparatur schaltest du ihn in der Konsole wieder ein und schickst die verpassten Zustellungen mit „Erneut senden“ noch einmal, oder du holst sie mit GET /events nach, 30 Tage zurück.
Reihenfolge und Doppelte
- Reihenfolge: nicht garantiert. Ereignisse eines Objekts können in anderer Reihenfolge ankommen, etwa ein Wiederholungsversuch nach einem späteren Ereignis. Der neueste Stand gewinnt nach
updated_atdes Objekts:data.object.updated_atist der Zeitpunkt der Änderung, die das Ereignis meldet. Behalte den Stand mit dem jüngstenupdated_atund übergeh einen älteren. - Doppelte: möglich, etwa wenn deine Antwort unterwegs verloren geht. Die
iddes Ereignisses entscheidet. - Stand im Umschlag:
data.objectwird beim ersten Versand gebaut und danach eingefroren; bei Bestellungen und Reservierungen sindstatusundupdated_atdie der Änderung, die das Ereignis meldet. Brauchst du sicher den aktuellen Stand, lies das Objekt mitGETnach. - Felder deiner App:
external_idundacknowledged_atstehen in Ereignissen immer aufnull, denn ein Umschlag geht an alle Empfänger gleich. Die Antworten der Routen tragen sie.
Zustellungen und erneut senden
Unter Webhooks stehen alle Zustellungen an deine Adresse, aus allen verbundenen Betrieben, filterbar nach Stand, Art, Betrieb und Zeitraum. Die Einzelansicht einer Zustellung zeigt den Umschlag, die gesendeten Kopfzeilen samt Signatur und jeden Versuch: Zeitpunkt, automatisch oder von Hand, HTTP-Status, Dauer, Fehler und den Anfang der Antwort deines Servers (bis 2.000 Zeichen).
„Erneut senden“ schickt eine Zustellung noch einmal, mit demselben Umschlag und derselben id. Das zählt nicht gegen die 8 automatischen Versuche. „Erneut senden“ und „Test-Ereignis senden“ gehen zusammen höchstens 10-mal je Minute und App, die Knöpfe im Reiter Ereignisse des Test-Betriebs eingeschlossen.
Test-Ereignisse
„Test-Ereignis senden“ schickt eine Art deiner Wahl an deine Adresse, auch eine, die du nicht abonniert hast, dazu ping. Die Daten kommen aus deinem Test-Betrieb, wo es welche gibt, sonst sind es Beispieldaten; livemode ist immer false. Danach ist es eine Zustellung wie jede andere: signiert, mit Wiederholungen und im Protokoll. Die Verbindung zum Test-Betrieb braucht dafür das passende Leserecht.
Echte Abläufe im Test-Betrieb lösen echte Ereignisse aus: eine Testbestellung order.created, ein Statuswechsel in der Konsole order.status_changed, eine Pause store.paused.
Für die Entwicklung auf dem eigenen Rechner brauchst du eine öffentliche Adresse, etwa über einen Tunnel. Oder du nimmst GET /events, das braucht keine.
Eigene Webhooks eines Betriebs
Ein Inhaber kann für seinen Betrieb auch selbst Webhooks einrichten, ohne App, etwa für ein eigenes System oder einen Automatisierungsdienst: im Dashboard unter Einstellungen, Integrationen, bis zu fünf Adressen. Sie haben dieselbe Form; connection_id ist dort null, und es gibt keine app.*-Ereignisse. Mehr dazu im Handbuch.
Etwas unklar geblieben? Schreib an support@flavoso.com und nenne die Kennung aus X-Request-Id. Damit finden wir deine Anfrage im Protokoll.