Direct API
Your backend calls the API after the user accepts in your own interface. You keep full control of the experience; 99zebras records and seals the acceptance.
POST /v1/acceptances For developers
Keep your own signup, checkout, or onboarding UI. 99zebras captures the acceptance, seals it server-side, and returns a record with a verification link. Integrate the way that fits your stack.
Quickstart
Send the published agreement version and your own reference for the end user. The response carries the sealed hash and a public verification URL. Pass an Idempotency-Key so a retry can never create a duplicate.
POST https://api.99zebras.com/v1/acceptances
Authorization: Bearer zsk_…
Idempotency-Key: order-1234-acceptance
Content-Type: application/json
{
"agreement_version_id": "0193e8c7-…",
"end_user_ref": "user-42@example.com"
}
→ 201 Created
{
"id": "019e8a4b-…",
"accepted_at": "2026-05-20T14:23:01Z",
"sealed_event_hash": "0123…cdef",
"verification_url": "https://app.99zebras.com/v/vrf_…",
"acceptance_method": "api_direct"
} Integration methods
Every method produces the same sealed record and the same verification link. Pick the one that fits where your interface lives.
Your backend calls the API after the user accepts in your own interface. You keep full control of the experience; 99zebras records and seals the acceptance.
POST /v1/acceptances Drop an iframe into any page. The user accepts inside it, and the frame posts a zebras:accepted message back to your page with the verification token. Locked to your sites by an allowed-origins list.
<iframe src="https://app.99zebras.com/embed/:slug/:agreement?pk=zpk_…"> Link or redirect to a 99zebras-hosted, branded agreement page. No front-end work. The user accepts and returns with a verification URL.
https://app.99zebras.com/a/:slug/:agreement Mint a pre-bound acceptance URL for a specific end-user reference and deliver it by email or your own automation. One at a time, or in bulk.
POST /v1/share_links @99zebras/browser-sdk handles capture from the browser: a headless captureAcceptance(), an accessible openConsentModal(), and renderAgreement() for inline rendering. Available now in early access.
captureAcceptance() · openConsentModal() · renderAgreement() Request access Under the hood
You capture an acceptance over one of the methods above. From there the platform takes over — here is what it does with the record, and why the result holds up when someone checks it later.
The hash and signature are produced on our servers, not in the client. A compromised browser or device cannot forge a valid seal.
The sealed record is written once and never edited in place. Nothing in the original acceptance is overwritten after the fact.
Each record carries a content hash and a digital signature. Change one byte of the terms, the time, or the metadata and verification fails.
SHA-256 · Ed25519A third-party timestamp authority co-signs the moment of acceptance, so the time does not rest on our clock or yours.
RFC 3161Personal data is encrypted and held apart from the evidence, so it can be erased on request without destroying the record of acceptance.
AES-256-GCMSealed bundles live in immutable, object-locked storage. Once written, they cannot be altered or deleted before their retention period ends.
Verification
Each acceptance comes back with a verification_url anyone can open — no account, and no call to us. The same token serves a machine-readable bundle and the raw timestamp.
GET /v/:token Human-readable receipt page GET /v/:token.json Machine-readable evidence bundle GET /v/:token.tsr Raw RFC 3161 timestamp reply 99zebras
Create an account, generate a key, and capture your first acceptance in a few minutes. The free plan is enough to build against.