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

Mint a playground session

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
Ready.
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'"}'
Ready.
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"
Ready.
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"
Ready.
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

Ready.
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, freshness window, retries, dead-letter, idempotency — the full spec.

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.