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:
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.
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:
$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-2 … order-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).
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.