Getting started

Quickstart

In five minutes you'll create an ingest endpoint, sign an event, POST it, and watch the fifth order award a badge — read straight back off the participant summary. Every step is a real request against your tenant.

1. Create an ingest endpoint

In the dashboard open Ingestion → Endpoints and create one. The endpoint page shows two things you need: the endpoint URL (it ends in your unguessable endpoint key) and the signing secret. Copy both. Optionally set a default event type so events without an explicit event_type still resolve.

Keep the secret server-side

The signing secret is what proves an event really came from you. Treat it like a password — never ship it in browser or mobile code. If it leaks, rotate the endpoint.

2. Put the credentials in your shell

The rest of this guide uses two variables so nothing sensitive ends up inline:

Set up bash
export FLYHALF_URL="https://api.flyhalf.run/v1/ingest/YOUR_ENDPOINT_KEY"
export FLYHALF_SECRET="YOUR_SIGNING_SECRET"

3. Sign and POST an order

The endpoint authenticates each request by an X-Signature header: the lowercase hex sha256 HMAC of the exact raw body, keyed with the signing secret. Sign the same bytes you send — re-serialising the JSON after signing is the number-one cause of a 401.

Signed ingest — bash bash
BODY='{"event_type":"order.completed","participant":{"external_id":"cus_84h2"},"payload":{"total":349,"items":3},"idempotency_key":"order-1"}'

SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$FLYHALF_SECRET" | sed 's/^.* //')

curl -X POST "$FLYHALF_URL" \
  -H "X-Signature: $SIG" \
  -H "Content-Type: application/json" \
  -d "$BODY"

→ 202 {"event_id":"…","status":"received"}

Prefer PHP? Same algorithm — hash the JSON string you're about to send:

Signed ingest — PHP php
$body = json_encode([
    'event_type' => 'order.completed',
    'participant' => ['external_id' => 'cus_84h2'],
    'payload' => ['total' => 349, 'items' => 3],
    'idempotency_key' => 'order-1',
], JSON_UNESCAPED_SLASHES);

$sig = hash_hmac('sha256', $body, getenv('FLYHALF_SECRET'));

$ch = curl_init(getenv('FLYHALF_URL'));
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $body,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json', "X-Signature: $sig"],
    CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);

A 202 means accepted and queued — the response is {"event_id","status"}. Processing (matching flows, awarding points and badges) runs asynchronously; the ingest path never blocks on it.

4. Repeat to the fifth order

Send four more, bumping the idempotency_key each time (order-2order-5) so each counts as a distinct event. Reusing a key is a safe no-op that returns the original event — see Idempotency. Assuming you've set up a "5th order" flow in the dashboard, the fifth event awards the badge.

5. Read the result back

The participant summary is the profile-widget endpoint: balance, level, badges, streaks and leaderboard ranks in one call. It's an authenticated read, so use an API key bearer token (see Authentication).

Read the summary bash
curl https://api.flyhalf.run/v1/participants/cus_84h2/summary \
  -H "Authorization: Bearer fh_live_…"

→ 200 { "balances": { "points": 120 },
        "badges": [ { "key": "loyal-buyer", "tier": "gold", "awarded_at": "…" } ],
        "leaderboards": [ { "key": "weekly", "rank": 3, "score": 120 } ] }

Nothing showing yet? Check the event log

Every event is visible in Ingestion → Event log with its status and payload. A quarantined status means the body was missing event_type or participant.external_id; a badge that didn't award means the flow trigger or conditions didn't match — the flow run trace tells you which node stopped.

Where to next

You've done the whole loop. Read Signing events for the canonical HMAC reference, or Events for the full body convention.