End-to-End Examples

Last updated 23 May 2026

Four end-to-end webhook examples, a JSON acknowledgement, a website form, an authenticated lookup, and a ping vs event branch

Complete round trips you can adapt. Each shows the incoming request, the workflow steps, the tokens used, and the response.

All examples assume a published virtual workflow whose starting step is the endpoint listener (see Creating an endpoint).

Example 1: Receive a webhook and acknowledge

Goal: another system tells Flexie an invoice was raised; Flexie records a note on the matching contact and replies "received."

The request the other system sends:

curl -X POST https://your-flexie/listener/abc123…/def456… \
  -H "Content-Type: application/json" \
  -d '{
        "invoice_amount": 2500,
        "customer_email": "john@example.com"
      }'

The workflow:

  1. Decision (virtual condition): {{ __data.customer_email }} is not empty
  1. Action, Create a note on the contact found by email, body: Invoice raised: {{ formatCurrency(__data.invoice_amount, "€") }}.
  2. Endpoint response (return_type = data): { "status": "received" }.

Endpoint listener settings: return_type = continue (so step 3 produces the reply), workflow run mode = Sync.

Key point: the body fields are read from the __data namespace, {{ __data.customer_email }}, {{ __data.invoice_amount }}, not at the top level.

Example 2: Create-or-update a contact from a website form

Goal: your website posts a sign-up; Flexie creates the contact if new, updates it if it exists, and redirects the visitor to a thank-you page.

The request (form-encoded, straight from a browser):

POST /listener/…/…
Content-Type: application/x-www-form-urlencoded

first_name=John&last_name=Doe&email=john@example.com

Endpoint listener settings: allow_cors = on, cors_domains = https://www.yoursite.com (because a browser calls it directly); return_type = redirect; return_redirect = https://www.yoursite.com/thanks?email={{ __data.email | url_encode }}.

The workflow:

  1. Action, Store a value existing = {{ findOne("contact", "email", __data.email).id | default("") }}.
  2. Decision (virtual condition): {{ __data.existing }} is not empty

The redirect (set on the listener) sends the visitor on regardless of branch.

If you would rather return JSON to a front-end script than redirect, set return_type = data and return { "ok": true }.

Example 3: A small custom lookup API (authenticated)

Goal: an internal tool asks Flexie for a contact's lifetime value and gets JSON back. Only the tool may call it.

Endpoint listener settings:

The request:

curl https://your-flexie/listener/…/… \
  -H "Authorization: Bearer <JWT signed HS256, iss=internal-tools>" \
  -H "Content-Type: application/json" \
  -d '{ "contact_id": 42 }'

(The token must be signed with HS256, HS384, or HS512 and carry iss = internal-tools.)

The workflow:

  1. Endpoint response (return_type = data):
{
  "contact_id": {{ __data.contact_id }},
  "lifetime_value": {{ findDealsValue("contact", "won", "*", __data.contact_id) }},
  "open_cases": {{ getCaseStats("contact", __data.contact_id).open }}
}

Because the workflow is Sync, the response is produced within the request and returned straight to the tool. An unauthenticated or wrongly-signed call is rejected before any step runs.

Example 4: Branch the reply by what arrived

Goal: one endpoint handles both "ping" health checks and real events.

The workflow:

  1. Decision (virtual condition): {{ __data.type }} equals ping

Listener: return_type = continue, workflow run mode = Sync. Two different replies from one endpoint, chosen by a virtual condition.

Recurring lessons from these examples

Back to