OpenFence

OpenFence Quick Start

Watch a real signed delivery happen, in 2 minutes. No install, no CLI, no signup.

A single device enters a 100m geofence in San Francisco. Domain-agnostic — your use case looks the same.
1

Get a playground API key

We'll ask OpenFence for a 24-hour playground API key. No signup — anyone can mint one. The key gives you access to a sandboxed tenant with playground rate limits.

curl -X POST https://api.openfence.ai/api/v1/playground/sessions
Idle
2

Create a webhook subscription

We'll register a webhook subscription whose target URL points at OpenFence's own echo receiver — https://api.openfence.ai/api/v1/playground/echo/<your-session-id>. The loopback is visible by design: the demo IS the real delivery path.

curl -X POST https://api.openfence.ai/api/v1/webhooks \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"target_url": "https://api.openfence.ai/api/v1/playground/echo/'$SESSION_ID'"}'
Idle
3

Fire a test delivery

We'll trigger a synthetic geofence.enter event for this subscription. OpenFence will sign it and POST it to the loopback URL — the same delivery worker that ships real production events.

curl -X POST https://api.openfence.ai/api/v1/webhooks/$SUB/test \
  -H "Authorization: Bearer $KEY"
Idle
4

Receive the signed delivery

We poll the session's inbox for the delivery OpenFence just sent itself. When it lands, we see the raw body bytes and all X-OpenFence-* headers — bit-identical to what your real receiver would see in production.

curl https://api.openfence.ai/api/v1/playground/sessions/$SESSION/inbox \
  -H "Authorization: Bearer $KEY"
Idle
5

Verify the signature in your browser

HMAC-SHA256 over "{timestamp}.{body_bytes}" keyed by the signing secret from step 2. We run it right here in your browser via WebCrypto — no Node, no Python required.

// Parse signature header: "t=<unix>,v1=<hex>"
const sig = entry.headers['x-openfence-signature'];
const parts = Object.fromEntries(sig.split(',').map(s => s.split('=')));
const body = Uint8Array.from(atob(entry.body_b64), c => c.charCodeAt(0));
const signedInput = new TextEncoder().encode(parts.t + '.');
const input = new Uint8Array(signedInput.length + body.length);
input.set(signedInput);
input.set(body, signedInput.length);
const key = await crypto.subtle.importKey(
  'raw', new TextEncoder().encode(signingSecret),
  { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']
);
const sigBytes = Uint8Array.from(parts.v1.match(/../g).map(h => parseInt(h, 16)));
const ok = await crypto.subtle.verify('HMAC', key, sigBytes, input);
// ok === true ✓

Building this in your own stack? Node sample · Go sample · Python sample

Idle
What just happened? Payload anatomy

The OpenFence delivery worker built a signed input as f"{timestamp}.".encode("ascii") + body_bytes — the timestamp followed by a literal period followed by the exact bytes of the JSON body.

It then computed HMAC_SHA256(signing_secret, signed_input), encoded the digest as 64 hex characters, and sent it in the X-OpenFence-Signature header as t=<unix>,v1=<hex>.

Your verifier reconstructs the signed input from the timestamp + raw body bytes, runs the same HMAC, and compares — with a constant-time comparator (see the verify-webhooks page for why).

If anything tampers with the body or the timestamp in flight, the HMAC won't match. Try it ↓

Contract

Read the integration contract

Wire format, 8-step verifier algorithm, and Node/Go/Python samples — signature verification spec.

Reference

Event payload reference

Browse every field on every event type. Verify any event against your own session.

Real tenant

Request a real API key

Tell us what you're building. We'll set up a tenant for your use case.

Build your verifier

Node / Go / Python samples

Zero-dep, byte-equal to the samples that ship with the repo.