Webhook-Endpoint — Angebot importieren
Der Webhook-Endpoint legt ein einzelnes Angebot als Entwurf in deiner Flino-Organisation an. Er ist idempotent über die Kombination external_source + external_id, sodass derselbe Aufruf mehrfach abgesendet kein Duplikat erzeugt.
Endpoint
Abschnitt betitelt „Endpoint“POST https://<euer-projekt>.supabase.co/functions/v1/zapier-offer-importDen <euer-projekt>-Teil findest du in Einstellungen → Integrationen → API-Tokens in der Sektion „Zapier Webhook URL“.
Diese Supabase-Functions-URL ist die kanonische und dauerhafte Endpoint-Adresse für den Webhook. Eine eigene API-Subdomain (z. B. api.flino.io) ist aktuell nicht geplant — verwende die hier gezeigte URL als stabile Adresse.
Authentifizierung
Abschnitt betitelt „Authentifizierung“Header:
x-flino-token: <dein-api-token>Content-Type: application/jsonDen <dein-api-token> erstellst du wie unter API-Tokens beschrieben. Pro Token ist der Scope fest auf offer_import:write gesetzt.
Methode
Abschnitt betitelt „Methode“Nur POST wird akzeptiert. Andere Methoden geben 405 Method not allowed zurück.
Request Body — Felder
Abschnitt betitelt „Request Body — Felder“Der Body muss ein JSON-Objekt sein. Listen oder Strings werden mit 400 Request body must be a JSON object abgewiesen.
Pflichtfelder
Abschnitt betitelt „Pflichtfelder“| Feld | Typ | Beschreibung |
|---|---|---|
title | string, nicht leer | Titel des Angebots, z. B. „Website-Redesign Mustermann GmbH“ |
external_id | string, nicht leer | Eindeutige ID aus deinem System — Schlüssel für Idempotenz |
external_source | string, nicht leer | Quell-System, z. B. zoho, pipedrive, zapier, myform — wird intern in Kleinbuchstaben gespeichert |
Optionale Felder
Abschnitt betitelt „Optionale Felder“| Feld | Typ | Beschreibung / Default |
|---|---|---|
amount | number | null | Angebotsbetrag in Hauptwährungs-Einheit (z. B. 1500.00) |
currency | string | EUR (Default), USD oder CHF. Andere Werte werden mit 400 abgewiesen. |
contact_name | string | null | Voller Name. Flino splittet automatisch: das letzte Wort wird zum Nachnamen, der Rest zum Vornamen. Wird nur zusammen mit contact_email verwendet. |
contact_email | string | null | Muss ein valides E-Mail-Format haben. Leerzeichen am Rand werden entfernt, gespeichert wird in Kleinbuchstaben. Ohne contact_email bekommt das Angebot keinen Kontakt. |
company_name | string | null | Firmenname; Flino legt eine neue Firma an oder verwendet eine bestehende |
offer_number | string | null | Eigene Angebotsnummer |
valid_until | string | null | ISO-Datum, z. B. 2026-12-31. Ungültige Datumswerte werden ignoriert (Wert bleibt null). |
Konsumiert / abgeleitet
Abschnitt betitelt „Konsumiert / abgeleitet“- Anrede und Sprache werden im Phase-1-Webhook nicht übermittelt — der Kontakt bekommt sie manuell oder beim ersten Versand-Modal nachgepflegt.
- Adresse wird im Phase-1-Webhook nicht übermittelt.
Idempotenz
Abschnitt betitelt „Idempotenz“Flino prüft pro Aufruf, ob in deiner Organisation bereits ein Angebot mit derselben Kombination external_source + external_id existiert.
- Existiert es: kein neues Angebot, Antwort
200mitcreated: false, der bestehendenoffer_idund ihrem aktuellenstatus. Ein solcher Aufruf belegt keinen Platz im Angebots-Kontingent. - Existiert es nicht: neues Angebot wird angelegt, Antwort
200mitcreated: true.
Das ist nützlich, wenn dein Quell-System Retries macht — du läufst nicht in Duplikate.
Angebots-Kontingent
Abschnitt betitelt „Angebots-Kontingent“Ein über den Webhook neu angelegtes Angebot zählt wie ein in der App angelegtes auf das Angebots-Kontingent deines Tarifs (siehe Abrechnung). Flino reserviert den Platz, bevor das Angebot angelegt wird. Löschst du den Entwurf, ohne ihn zu versenden, wird der Platz wieder frei.
Ist das Kontingent aufgebraucht, legt Flino nichts an und antwortet mit 403:
{ "success": false, "error": "OFFER_QUOTA_EXHAUSTED", "code": "OFFER_QUOTA_EXHAUSTED", "message": "Dein Angebotskontingent für diesen Monat ist aufgebraucht. Es erneuert sich mit dem nächsten Abrechnungszeitraum. Mehr Kontingent gibt es mit einem höheren Plan."}Die Antwort enthält zusätzlich ein Feld balances mit dem aktuellen Stand. Schick denselben Aufruf nach der Erneuerung des Kontingents oder nach einem Wechsel auf einen größeren Plan erneut; dank Idempotenz entsteht dabei kein Duplikat.
Erfolgs-Response
Abschnitt betitelt „Erfolgs-Response“Status 200 OK:
{ "success": true, "created": true, "offer_id": "8d3f7e09-1e0e-44e5-b6c3-49e3a7c2a131", "status": "draft"}Bei Idempotenz-Treffer:
{ "success": true, "created": false, "offer_id": "8d3f7e09-1e0e-44e5-b6c3-49e3a7c2a131", "status": "draft"}Vollständiges Beispiel
Abschnitt betitelt „Vollständiges Beispiel“Request
Abschnitt betitelt „Request“POST /functions/v1/zapier-offer-import HTTP/1.1Host: <euer-projekt>.supabase.coContent-Type: application/jsonx-flino-token: 9a3f...c1e2
{ "title": "Website-Redesign Mustermann GmbH", "amount": 4900, "currency": "EUR", "contact_name": "Anna Mustermann", "contact_email": "anna@mustermann.de", "company_name": "Mustermann GmbH", "offer_number": "ANG-2026-0042", "valid_until": "2026-06-30", "external_id": "deal_19238", "external_source": "zoho"}Response
Abschnitt betitelt „Response“{ "success": true, "created": true, "offer_id": "8d3f7e09-1e0e-44e5-b6c3-49e3a7c2a131", "status": "draft"}Fehlerfälle
Abschnitt betitelt „Fehlerfälle“| HTTP-Status | Body error | Bedeutung |
|---|---|---|
400 | Invalid JSON body | Body ist kein gültiges JSON |
400 | Request body must be a JSON object | Body ist Liste oder String |
400 | Field 'title' is required and must be a non-empty string | title fehlt oder leer |
400 | Field 'external_id' is required and must be a non-empty string | external_id fehlt oder leer |
400 | Field 'external_source' is required and must be a non-empty string | external_source fehlt oder leer |
400 | Field 'currency' must be one of: EUR, USD, CHF | unzulässige Währung |
400 | Field 'amount' must be a number | amount als String oder Boolean |
400 | Field 'contact_email' is not a valid email address | Mail-Format ungültig |
401 | Missing x-flino-token header | Header fehlt |
401 | Invalid or unknown token | Token nicht gefunden |
401 | Token has been revoked | Token wurde widerrufen |
403 | Token lacks required scope: offer_import:write | Scope passt nicht |
403 | OFFER_QUOTA_EXHAUSTED | Angebots-Kontingent aufgebraucht, siehe oben |
405 | Method not allowed | nur POST erlaubt |
429 | Too many requests | Limit überschritten, siehe Rate-Limits |
500 | Organisation owner not found | Org-Konfigurationsproblem — Support kontaktieren |
500 | Failed to create offer: ... | Datenbank-Problem |
503 | Offer quota reservation failed | Das Kontingent konnte gerade nicht geprüft werden, nichts wurde angelegt; später erneut senden |
Alle Fehler-Antworten haben das Schema:
{ "success": false, "error": "<Klartext-Beschreibung>"}Einige Fehler tragen zusätzlich ein Feld code für die maschinelle Auswertung: RATE_LIMITED (429), OFFER_QUOTA_EXHAUSTED (403) und OFFER_QUOTA_ERROR (503).
Was Flino aus deinem Aufruf macht
Abschnitt betitelt „Was Flino aus deinem Aufruf macht“- Token validieren — SHA-256-Lookup, Revoke-Check, Scope-Check, danach das Limit je Token.
- Body prüfen — Pflichtfelder, Währung, Betrag, E-Mail-Format.
- Idempotenz prüfen — gibt’s das Angebot schon? Wenn ja, dasselbe zurückgeben.
- Firma anlegen oder wiederverwenden — wenn
company_namegesetzt ist, sucht Flino zuerst eine vorhandene Firma mit demselben Namen in deiner Org; Groß- und Kleinschreibung sowie Rechtsformzusätze wie „GmbH“ oder „AG“ spielen dabei keine Rolle. Findet sie keine, wird eine neue angelegt. - Kontakt anlegen oder wiederverwenden — nur wenn
contact_emailgesetzt ist, mit der E-Mail-Adresse als Schlüssel. Bei einem vorhandenen Kontakt füllt Flino nur leere Felder (etwa den Namen), überschreibt aber nichts. Ohnecontact_emailbleibt das Angebot ohne Kontakt. - Kontingent reservieren — ein Platz im Angebots-Kontingent, siehe oben.
- Angebot als
draftanlegen — mit Versandstatusnot_sent, Quellezapier, deinenexternal_idundexternal_sourcefür die nächste Idempotenz-Prüfung.
Was passiert nach dem Import
Abschnitt betitelt „Was passiert nach dem Import“- Das Angebot erscheint unter Angebote mit dem Hinweis Zapier-Import und in der Pipeline mit Status Neu.
- Du öffnest es manuell, ergänzt fehlende Daten (Kontakt, Anrede, Mail-Adresse, Beschreibung) und versendest es wie gewohnt aus Flino.
- Es startet keine Sequenz, es wird keine Mail verschickt — der Webhook ist reines Anlegen.
Rate-Limits
Abschnitt betitelt „Rate-Limits“Je Token sind 60 Anfragen pro Minute erlaubt. Darüber antwortet der Endpunkt mit 429:
{ "success": false, "error": "Too many requests", "code": "RATE_LIMITED"}Warte dann bis zur nächsten Minute und schick den Aufruf erneut. Anfragen mit unbekannten Tokens teilen sich zusätzlich ein gemeinsames Limit; wird es überschritten, bekommen auch sie 429 statt 401.
Wiederholungen sind unkritisch: Die Idempotenz über external_source + external_id sorgt dafür, dass ein erneut gesendeter Aufruf kein doppeltes Angebot erzeugt.
Wenn du Massen-Importe planst (mehrere hundert Angebote auf einmal), staffel die Aufrufe unterhalb des Limits und prüf vorher, ob dein Angebots-Kontingent dafür reicht.