OVERVIEW
IntroductionAuthenticationBase URLError handling
ENDPOINTS
POST Create requestGET Poll status
WEBHOOKS
Webhook deliverySignature verificationPayload reference
TRUST SCORE
How scoring worksSignal reference

API Reference

The HorizonLink Proximity Verification API lets your backend verify that a HorizonLink user is physically present within a defined radius of a target location. Requests are answered in-app, scored against hardware sensor data and server signals, and delivered to your webhook.

AUTHENTICATION

All API requests require your API key in the X-API-Key header.

X-API-Key: hlv_your_api_key_here

Keys are generated from your dashboard or the HorizonLink app (Settings → API Access). Each key is shown once at creation — store it in an environment variable.

⚠️ Never commit your API key to source control or expose it in client-side code.

BASE URL

https://gjxpulfkxhpoghcsklsx.supabase.co/functions/v1

ERROR HANDLING

All errors return JSON with an error field and an appropriate HTTP status code.

{ "error": "Missing required fields" }
StatusMeaning
400Bad request — missing or invalid fields
401Invalid or missing API key
404Target user not found
409User already has a pending request
429Monthly request limit reached (free plan: 100/mo)
500Internal server error

POSTCREATE PROXIMITY REQUEST

POST https://gjxpulfkxhpoghcsklsx.supabase.co/functions/v1/v1-proximity-create

Creates a new proximity verification request. The target user receives a push notification in the HorizonLink app and can approve or decline.

Request body (JSON)

FieldTypeRequiredDescription
target_user_idstring (UUID)YesThe HorizonLink user's UUID. Obtainable from the user via deep link or your own auth flow.
radius_mnumberYesRadius in metres. The verification passes only if the user is within this distance.
min_trust_scorenumberNoIf set, the webhook meets_min_score field will reflect whether the score meets this threshold (0–100).
webhook_urlstring (URL)NoOverride the default webhook URL for this specific request.
expires_in_secnumberNoHow long (seconds) the request stays open. Default: 300 (5 min). Max: 3600.

Example request

curl -X POST https://gjxpulfkxhpoghcsklsx.supabase.co/functions/v1/v1-proximity-create \ -H "X-API-Key: hlv_your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "target_user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "radius_m": 100, "min_trust_score": 70 }'

Success response — 200

{ "request_id": "uuid-of-the-created-request", "status": "pending", "expires_at": "2026-05-07T12:35:00.000Z" }

GETPOLL REQUEST STATUS

GET https://gjxpulfkxhpoghcsklsx.supabase.co/functions/v1/v1-proximity-status?request_id={uuid}

Retrieves the current status and result of a proximity request. Poll this endpoint until status is no longer pending, or use webhooks for push delivery.

Query parameters

ParamTypeRequiredDescription
request_idstring (UUID)YesThe request ID returned by the create endpoint.

Example request

curl "https://gjxpulfkxhpoghcsklsx.supabase.co/functions/v1/v1-proximity-status?request_id=uuid-here" \ -H "X-API-Key: hlv_your_api_key_here"

Success response — 200

{ "request_id": "uuid", "status": "approved", // pending | approved | out_of_range | declined | expired "trust_score": 85, // 0–100, null while pending "trust_flags": ["near_boundary"], // array of failed signal types "distance_m": 47, // null while pending "meets_min_score": true, // null if min_trust_score not set "created_at": "2026-05-07T12:30:00.000Z", "expires_at": "2026-05-07T12:35:00.000Z" }

Status values

StatusMeaning
pendingRequest sent, waiting for user to respond
approvedUser responded and was within the radius
out_of_rangeUser responded but was outside the radius
declinedUser explicitly declined the request
expiredUser did not respond before expires_at

WEBHOOK DELIVERY

When a request is resolved, HorizonLink POSTs a signed JSON payload to your webhook URL. Webhooks are attempted up to 3 times with exponential backoff (1s → 2s) on failure. Your endpoint must return a 2xx status within 5 seconds.

Webhook delivery is fire-and-forget — it does not block the user's response in the app. Use polling (v1-proximity-status) for time-critical flows.

SIGNATURE VERIFICATION

Every webhook request includes an X-HL-Signature header containing an HMAC-SHA256 signature of the raw request body, signed with your webhook secret.

Your webhook secret is set during registration (or can be updated in your dashboard). Verify every webhook to prevent replay attacks.

Verification — Node.js example

import crypto from 'crypto'; function verifyWebhook(rawBody: string, signature: string, secret: string): boolean { const expected = 'sha256=' + crypto .createHmac('sha256', secret) .update(rawBody) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected), ); } // In your webhook handler: const sig = req.headers['x-hl-signature']; const raw = req.rawBody; // unparsed request body string if (!verifyWebhook(raw, sig, process.env.HL_WEBHOOK_SECRET)) { return res.status(401).send('Invalid signature'); }

WEBHOOK PAYLOAD REFERENCE

{ "event": "proximity.result", "request_id": "uuid", "status": "approved", "trust_score": 85, "distance_m": 47, "meets_min_score": true, "verified_at": "2026-05-07T12:31:44.000Z", "flags": [] }
FieldTypeDescription
eventstringAlways "proximity.result"
request_idstringUUID of the request
statusstringFinal status (see status values above)
trust_scorenumberComposite trust score 0–100
distance_mnumberDistance in metres from the target location
meets_min_scoreboolean|nullWhether score ≥ min_trust_score. null if not set.
verified_atstringISO 8601 timestamp of when the result was recorded
flagsstring[]Array of failed signal type names

HOW THE TRUST SCORE WORKS

The trust score (0–100) is a composite measure computed server-side from both server-verifiable signals and client-reported hardware sensor readings. Starting at 100, deductions are applied for each anomaly.

Hard failures (mock location provider, out-of-range distance) immediately set the score to 0 and abort further checks.

SIGNAL REFERENCE

SignalSourceImpactDescription
mock_providerClient / OSHard fail (−100)OS reports mock location provider active
out_of_rangeServerHard fail (−100)Haversine distance > radius_m
near_boundaryServer−10Distance > 75% of radius
gps_accuracyServer0 / −20 / −35±30m = 0, ±60m = −20, >60m = −35
speedServer0 / −20 / −50Normal = 0, >54km/h = −20, >180km/h = −50
response_timeServer−10 / 0 / +5>45s = −10, 15–45s = 0, <15s = +5
ip_gps_countryServer0 / −20IP country ≠ GPS country = −20
accel_varianceClient−25Low accelerometer variance (device stationary / spoofed)
baro_altitudeClient−15Barometer altitude disagrees with GPS altitude by >120m
speed_sanityClient−25Consecutive GPS fixes imply >900 km/h travel
Client sensor signals (accel, baro, speed sanity) are capped at a combined −40pt deduction to limit the impact of a manipulated client sending false signals.