HTTP API

Send events to Flowfy from a server, with no SDK involved.

Anything that can make an HTTPS request can send events. Use this for orders confirmed after the shopper has left, refunds issued from an admin panel, or any system with no browser in it.

Base URL: https://edge.flofy.co

Endpoints

EndpointShort formtypeFor
/v1/track/v1/ttrackSomething happened
/v1/identify/v1/iidentifyWho someone is
/v1/page/v1/ppageA page view
/v1/screen/v1/sscreenA screen view in an app
/v1/group/v1/ggroupAttach someone to a company
/v1/alias/v1/aaliasTwo IDs, one person
/v1/batch/v1/bSeveral events at once

All are POST with Content-Type: application/json. The short forms behave identically to the long ones.

Authentication

Your write key goes in an HTTP Basic header, as the username with an empty password. Base64-encode the key followed by a colon:

printf 'YOUR_WRITE_KEY:' | base64
Authorization: Basic <that value>

The trailing colon is required, and a non-empty password is rejected.

You can pass the key as a query parameter instead — ?writeKey=<base64 of the key> — which is what the browser SDK does, to keep requests free of a CORS preflight. When both are present, the query parameter wins.

A request

curl -X POST https://edge.flofy.co/v1/track \
  -H "Authorization: Basic $(printf 'YOUR_WRITE_KEY:' | base64)" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "track",
    "event": "Order Completed",
    "userId": "customer_9931",
    "messageId": "8f14e45f-ea0a-4c1f-9f2c-3b7d1a0e5c62",
    "timestamp": "2026-08-25T10:31:00.000Z",
    "properties": {
      "order_id": "ORD-12345",
      "currency": "SAR",
      "value": 523.17,
      "products": [
        { "product_id": "P-4471", "name": "Wireless Headphones", "price": 199.99, "quantity": 1 }
      ]
    }
  }'

The envelope

FieldRequiredMeaning
typeYesMust match the endpoint
eventOn trackThe event name
messageIdYesUnique per event. A UUID is fine
timestampYesWhen it happened — ISO 8601, UTC
userIdOne of the twoYour ID for a known person
anonymousIdOne of the twoAn ID for someone not signed in
propertiesOn trackThe event's properties
traitsOn identifyWhat you know about the person
contextNoPage, campaign, IP, user agent
integrationsNoTurn individual destinations on or off

messageId is how a retry is recognised as the same event rather than a second one. Generate it once per event and reuse it if you retry.

Envelope fields are camelCase — userId, messageId. Everything inside properties and traits is snake_case — order_id, first_name. The split is deliberate: the envelope describes the message, the properties describe your store.

Context

The SDK fills this in from the browser. Over HTTP you send what you have, and all of it is optional:

{
  "context": {
    "ip": "203.0.113.9",
    "userAgent": "Mozilla/5.0 …",
    "locale": "ar-SA",
    "timezone": "Asia/Riyadh",
    "page": {
      "url": "https://store.example.com/checkout",
      "path": "/checkout",
      "referrer": "https://www.google.com/"
    },
    "campaign": {
      "source": "facebook",
      "medium": "cpc",
      "name": "ramadan-2026"
    }
  }
}

ip and userAgent are the two worth the effort. Meta, TikTok and Snapchat use them to match a server-side conversion to an ad view, and without them a share of your orders goes unattributed. Send the shopper's address, not your server's.

Batching

{
  "batch": [
    {
      "type": "identify",
      "userId": "customer_9931",
      "messageId": "",
      "timestamp": "",
      "traits": { "email": "sara@example.com" }
    },
    {
      "type": "track",
      "event": "Order Completed",
      "userId": "customer_9931",
      "messageId": "",
      "timestamp": "",
      "properties": { "order_id": "ORD-12345", "currency": "SAR", "value": 523.17 }
    }
  ]
}

Every element is a complete event with its own type, messageId and timestamp. Elements are validated before any of them is accepted, so a single malformed entry rejects the whole request — which is what makes the request safe to retry as a unit.

Limits

Single event256 KB
Batch request5 MB
Events per batch1,000

Responses

CodeMeaning
204Received
400Missing or malformed write key, invalid JSON, or a bad batch element
413More than 1,000 events in one batch
503Flowfy could not accept the event — retry with backoff

There is no response body on success. A 204 means the request was well-formed, not that the write key matches a source: a valid-looking key for a source that does not exist is accepted here and dropped later. Confirm in Live events on the source's page in Flowfy.

When something is wrong

Work down this list:

  1. Is the header right? Basic, a space, then base64 of key: — with the colon.
  2. Does type match the endpoint? A track body posted to /v1/identify will not do what you meant.
  3. Is identity in the body? userId or anonymousId has to be in the JSON. A header does not count.
  4. Is timestamp ISO 8601 in UTC? 2026-08-25T10:31:00.000Z, not a Unix epoch.
  5. Are the properties snake_case? order_id, not orderId. The wrong casing is accepted and then ignored, which looks like a delivery problem but is not.
  6. Does the event name match the reference? Check the event reference.