Webhook endpoint — import an offer
The webhook endpoint creates a single offer as a draft in your Flino organisation. It is idempotent over the combination external_source + external_id, so the same call sent multiple times does not produce a duplicate.
Endpoint
Section titled “Endpoint”POST https://<your-project>.supabase.co/functions/v1/zapier-offer-importYou find the <your-project> part in Settings → Integrations → API tokens, in the “Zapier Webhook URL” section.
This Supabase Functions URL is the canonical and permanent endpoint address for the webhook. A separate API subdomain (e.g. api.flino.io) is not planned at the moment — use the URL shown here as the stable address.
Authentication
Section titled “Authentication”Headers:
x-flino-token: <your-api-token>Content-Type: application/jsonGenerate <your-api-token> as described in API tokens. Per token the scope is fixed to offer_import:write.
Method
Section titled “Method”Only POST is accepted. Other methods return 405 Method not allowed.
Request body — fields
Section titled “Request body — fields”The body must be a JSON object. Arrays or strings are rejected with 400 Request body must be a JSON object.
Required fields
Section titled “Required fields”| Field | Type | Description |
|---|---|---|
title | string, non-empty | Offer title, e.g. “Website redesign Mustermann GmbH” |
external_id | string, non-empty | Unique ID from your system — the key for idempotency |
external_source | string, non-empty | Source system, e.g. zoho, pipedrive, zapier, myform — stored internally in lowercase |
Optional fields
Section titled “Optional fields”| Field | Type | Description / default |
|---|---|---|
amount | number | null | Offer amount in main currency unit (e.g. 1500.00) |
currency | string | EUR (default), USD, or CHF. Other values are rejected with 400. |
contact_name | string | null | Full name. Flino splits it into first and last name automatically. |
contact_email | string | null | Must be in valid email format. Stored in lowercase. |
company_name | string | null | Company name; Flino creates a new company or reuses an existing one |
offer_number | string | null | Your own offer number |
valid_until | string | null | ISO date, e.g. 2026-12-31. Invalid dates are ignored (value stays null). |
Consumed / derived
Section titled “Consumed / derived”- Salutation and language are not transmitted in the Phase 1 webhook — they are added on the contact manually or in the first send dialog.
- Address is not transmitted in the Phase 1 webhook.
Idempotency
Section titled “Idempotency”For each call, Flino checks whether an offer with the same external_source + external_id already exists in your organisation.
- If it exists: no new offer, response
200withcreated: falseand the existingoffer_id. - If it does not exist: a new offer is created, response
200withcreated: true.
Useful when your source system retries — you don’t end up with duplicates.
Success response
Section titled “Success response”Status 200 OK:
{ "success": true, "created": true, "offer_id": "8d3f7e09-1e0e-44e5-b6c3-49e3a7c2a131", "status": "draft"}On idempotency hit:
{ "success": true, "created": false, "offer_id": "8d3f7e09-1e0e-44e5-b6c3-49e3a7c2a131", "status": "draft"}Full example
Section titled “Full example”Request
Section titled “Request”POST /functions/v1/zapier-offer-import HTTP/1.1Host: <your-project>.supabase.coContent-Type: application/jsonx-flino-token: 9a3f...c1e2
{ "title": "Website redesign Mustermann GmbH", "amount": 4900, "currency": "EUR", "contact_name": "Anna Mustermann", "company_name": "Mustermann GmbH", "offer_number": "ANG-2026-0042", "valid_until": "2026-06-30", "external_id": "deal_19238", "external_source": "zoho"}Response
Section titled “Response”{ "success": true, "created": true, "offer_id": "8d3f7e09-1e0e-44e5-b6c3-49e3a7c2a131", "status": "draft"}Error cases
Section titled “Error cases”| HTTP status | Body error | Meaning |
|---|---|---|
400 | Invalid JSON body | Body is not valid JSON |
400 | Request body must be a JSON object | Body is an array or a string |
400 | Field 'title' is required and must be a non-empty string | title missing or empty |
400 | Field 'external_id' is required and must be a non-empty string | external_id missing or empty |
400 | Field 'external_source' is required and must be a non-empty string | external_source missing or empty |
400 | Field 'currency' must be one of: EUR, USD, CHF | unsupported currency |
400 | Field 'amount' must be a number | amount sent as string or boolean |
400 | Field 'contact_email' is not a valid email address | invalid email format |
401 | Missing x-flino-token header | Header missing |
401 | Invalid or unknown token | Token not found |
401 | Token has been revoked | Token has been revoked |
403 | Token lacks required scope: offer_import:write | Scope mismatch |
405 | Method not allowed | Only POST allowed |
500 | Organisation owner not found | Org configuration issue — contact support |
500 | Failed to create offer: ... | Database error |
All error responses follow this schema:
{ "success": false, "error": "<plain-text description>"}What Flino does with your call
Section titled “What Flino does with your call”- Validate the token — SHA-256 lookup, scope check, revoke check.
- Check idempotency — does the offer already exist? If so, return the existing one.
- Create or reuse the company — if
company_nameis set, Flino looks for an existing company with the same name in your org. If none, a new one is created. - Create or reuse the contact — using
contact_email(when set) as the key; otherwise by name. Flino splitscontact_nameinto first and last name automatically. - Create the offer as
draft— with send statusnot_sent, sourcezapier, yourexternal_idandexternal_sourcefor the next idempotency check.
What happens after the import
Section titled “What happens after the import”- The offer lands in the offer import and in the pipeline with status New.
- You open it manually, fill in missing data (salutation, email address, description), and send it from Flino as usual.
- No sequence is started, no email is sent — the webhook only creates the offer.
Rate limits
Section titled “Rate limits”No hard rate limits documented at the moment. If you plan a bulk import (several hundred offers at once), spread the calls out and talk to support so your org doesn’t accidentally trip an anomaly check.
No Flino-specific rate limits at the moment. We reserve the right to throttle in case of abuse. Idempotency for retries is ensured via
external_id+ content hash — repeated calls with identical data don’t produce a duplicate offer.