Zum Inhalt springen

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.

POST https://<euer-projekt>.supabase.co/functions/v1/zapier-offer-import

Den <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.

Header:

x-flino-token: <dein-api-token>
Content-Type: application/json

Den <dein-api-token> erstellst du wie unter API-Tokens beschrieben. Pro Token ist der Scope fest auf offer_import:write gesetzt.

Nur POST wird akzeptiert. Andere Methoden geben 405 Method not allowed zurück.

Der Body muss ein JSON-Objekt sein. Listen oder Strings werden mit 400 Request body must be a JSON object abgewiesen.

FeldTypBeschreibung
titlestring, nicht leerTitel des Angebots, z. B. „Website-Redesign Mustermann GmbH“
external_idstring, nicht leerEindeutige ID aus deinem System — Schlüssel für Idempotenz
external_sourcestring, nicht leerQuell-System, z. B. zoho, pipedrive, zapier, myform — wird intern in Kleinbuchstaben gespeichert
FeldTypBeschreibung / Default
amountnumber | nullAngebotsbetrag in Hauptwährungs-Einheit (z. B. 1500.00)
currencystringEUR (Default), USD oder CHF. Andere Werte werden mit 400 abgewiesen.
contact_namestring | nullVoller Name. Flino splittet automatisch: das letzte Wort wird zum Nachnamen, der Rest zum Vornamen. Wird nur zusammen mit contact_email verwendet.
contact_emailstring | nullMuss ein valides E-Mail-Format haben. Leerzeichen am Rand werden entfernt, gespeichert wird in Kleinbuchstaben. Ohne contact_email bekommt das Angebot keinen Kontakt.
company_namestring | nullFirmenname; Flino legt eine neue Firma an oder verwendet eine bestehende
offer_numberstring | nullEigene Angebotsnummer
valid_untilstring | nullISO-Datum, z. B. 2026-12-31. Ungültige Datumswerte werden ignoriert (Wert bleibt null).
  • 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.

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 200 mit created: false, der bestehenden offer_id und ihrem aktuellen status. Ein solcher Aufruf belegt keinen Platz im Angebots-Kontingent.
  • Existiert es nicht: neues Angebot wird angelegt, Antwort 200 mit created: true.

Das ist nützlich, wenn dein Quell-System Retries macht — du läufst nicht in Duplikate.

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.

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"
}
POST /functions/v1/zapier-offer-import HTTP/1.1
Host: <euer-projekt>.supabase.co
Content-Type: application/json
x-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"
}
{
"success": true,
"created": true,
"offer_id": "8d3f7e09-1e0e-44e5-b6c3-49e3a7c2a131",
"status": "draft"
}
HTTP-StatusBody errorBedeutung
400Invalid JSON bodyBody ist kein gültiges JSON
400Request body must be a JSON objectBody ist Liste oder String
400Field 'title' is required and must be a non-empty stringtitle fehlt oder leer
400Field 'external_id' is required and must be a non-empty stringexternal_id fehlt oder leer
400Field 'external_source' is required and must be a non-empty stringexternal_source fehlt oder leer
400Field 'currency' must be one of: EUR, USD, CHFunzulässige Währung
400Field 'amount' must be a numberamount als String oder Boolean
400Field 'contact_email' is not a valid email addressMail-Format ungültig
401Missing x-flino-token headerHeader fehlt
401Invalid or unknown tokenToken nicht gefunden
401Token has been revokedToken wurde widerrufen
403Token lacks required scope: offer_import:writeScope passt nicht
403OFFER_QUOTA_EXHAUSTEDAngebots-Kontingent aufgebraucht, siehe oben
405Method not allowednur POST erlaubt
429Too many requestsLimit überschritten, siehe Rate-Limits
500Organisation owner not foundOrg-Konfigurationsproblem — Support kontaktieren
500Failed to create offer: ...Datenbank-Problem
503Offer quota reservation failedDas 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).

  1. Token validieren — SHA-256-Lookup, Revoke-Check, Scope-Check, danach das Limit je Token.
  2. Body prüfen — Pflichtfelder, Währung, Betrag, E-Mail-Format.
  3. Idempotenz prüfen — gibt’s das Angebot schon? Wenn ja, dasselbe zurückgeben.
  4. Firma anlegen oder wiederverwenden — wenn company_name gesetzt 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.
  5. Kontakt anlegen oder wiederverwenden — nur wenn contact_email gesetzt 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. Ohne contact_email bleibt das Angebot ohne Kontakt.
  6. Kontingent reservieren — ein Platz im Angebots-Kontingent, siehe oben.
  7. Angebot als draft anlegen — mit Versandstatus not_sent, Quelle zapier, deinen external_id und external_source für die nächste Idempotenz-Prüfung.
  • 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.

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.