Developer documentation
Getting started, concepts, authentication, the API reference, consent in your own forms, receipts, webhooks and embedding.
Getting Started
Everything you need to integrate with Overturo.
5-minute quickstart
-
1
Mint a decision token from your server
curl -X POST https://api.overturo.com/api/v1/decisions \ -H "Authorization: Bearer $OVERTURO_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"kind": "consent", "flow_id": "flw_..."}' -
2
Render it in your page
<overturo-decision token="dts_..."></overturo-decision> <script type="module" src="https://cdn.overturo.com/js/v1/overturo.js"></script> -
3
Receive the outcome via webhook
Configure a webhook URL in Application Settings โ Webhooks. Subscribe to
consent.granted/consent.denied. Same shape works for approvals (approval.granted/.denied/.countered).
Full walk-through: the Embedding tab.
Create Application
Register a new application and get your credentials.
Get API Token
Generate a bearer token for REST API access.
Make First Call
Try a live API call in the explorer.
The 7 Surfaces
Connect
Application registration, credentials, webhooks.
Consent
Consent flows, permission configuration, trust tiers.
Verify
Credential issuance, presentations, trust scores.
Protect
Data policies, access requests, transfers, licenses.
Agree
Agreements, multi-party signatures, quorum votes.
Delegate
Delegation grants, AI agents, emergency access.
Comply
Compliance modules, audit, evidence packages.
Documentation
API Reference
Endpoints, auth, response format, and error codes
Authentication Guide
5 auth methods: tokens, OAuth, publishable keys, agents, delegation
JavaScript SDK
Client-side decision embeds (consent + approval) via <overturo-decision> and postMessage v2
Ruby SDK
Server-side API calls, webhooks, and audit records
Building Blocks
Composable integration modules and surface contracts
Connectors
Third-party data provider integration guide
Publish a Block
Conformance testing, lifecycle, and marketplace publishing
API Explorer
Interactive request builder for testing API calls
Concepts & Vocabulary
The terms that show up across the SDK, API, and webhook examples โ what each one is, what it looks like, and where you'll see it.
The Decision primitive
A Decision is anything Overturo asks a principal to decide โ consenting to data sharing, approving an AI agent's action, signing an attestation. v1 ships two kinds: consent (a user permits your app to read or use their data) and approval (a human authorizes an action proposed by an AI agent or workflow).
Both kinds ride the same Trust Surface delivery layer โ same iframe, same SDK, same postMessage protocol, same webhook contract. The kind-specific work lives in the body of the decision (the question being asked), not the chrome around it.
Canonical entry point: the Embedding tab.
At a Glance
| Term | Prefix | In one line |
|---|---|---|
| Decision | dts_* | A bounded ask: consent or approval. |
| Application | tp_* | Your project on Overturo. |
| Publishable Key | pk_test_* / pk_live_* | Public credential โ safe to ship in browser code. |
| API Token | (opaque bearer) | Server-only credential โ full account access. |
| Flow | flw_* | A reusable consent template you author once. |
| Consent Session | (opaque) | A short-lived link between a user and a flow. |
| Consent Token | (opaque) | The user's receipt of consent. Verify server-side. |
| AI Agent Token | ra_agent_* | Bounded credential for AI agents acting on a user's behalf. |
| Delegation Grant | dlg_* | Permission for one human to act on another's behalf. |
| Audit Trail | โ | Tamper-proof record of every consent decision. |
1. Application
An Application is your project on Overturo. It owns the credentials you use to make API calls and the flows you publish to your users. You create one from Connect.
Each application gets a unique ID with format tp_<region>_<hashid> (for example tp_dev_7J580kDeZ6vGmnyxUaPAKblWโฆ in development, tp_us_โฆ or tp_eu_โฆ in production), a publishable key for client-side identification, and one or more API tokens for server-side calls. Webhook deliveries, audit-trail records, and rate limits are all scoped per-application.
You'll see it as: the application_id field in webhook payloads and API responses.
2. Publishable Key
A Publishable Key identifies your application from client-side code (browser, mobile, embedded SDK). It cannot read sensitive data, modify resources, or list other users โ only create consent sessions. Safe to commit to source control and ship in JavaScript bundles.
Two flavours: pk_test_* hits a sandbox; pk_live_* hits production. Sent on every request as the X-Publishable-Key header โ the SDK does this for you when you pass publishableKey to the constructor.
Find it in: Credentials โ Publishable Keys. Used in: JavaScript SDK initialization.
3. API Token
An API Token is a server-only bearer credential with full account-level access. Use it for server-to-server calls: creating applications, managing flows, fetching audit records, processing webhooks.
Treat like a password: never expose to a browser, never commit to source control, rotate regularly. Sent as Authorization: Bearer <token>. The token itself is an opaque high-entropy string with no marketed prefix; the surrounding record (creation time, scopes, last-used) has a api_token_* ID you'll see in the dashboard.
Find it in: Credentials โ API Tokens. Used in: Authentication ยง1.
4. Flow
A Flow is a reusable consent template you author once and reference for every consent session. It declares: which data fields the user is consenting to share, the purpose of use, retention, revocation policy, and the multi-step UX the user walks through.
A flow has a stable ID like flw_dev_V3d1oQ6Zโฆ (or flw_* in production). Once activated, a flow's terms are immutable โ to change them, you publish a new version; the old version keeps governing previously-collected consents.
You'll pass it as: flowId when creating a consent session.
5. Consent Session
Scope: the Consent Session and Consent Token concepts below describe the integration model for consent flows (Flows โ see ยง4). Other artifact types โ Agreements, Authorities, data deals โ have analogous lifecycle objects with different semantics; see "Other surface artifacts" at the end of ยง6.
A Consent Session is a short-lived link between a specific user and a specific Flow. You create one when a user is about to consent (the SDK creates it for you when you call overturo.consent({ flowId })); the user is then walked through the Flow's UX. The session expires whether or not the user finishes.
Carries a session_token used to render the embed and an embed_url the iframe loads. The session is the request to consent; the Consent Token below is the receipt.
How to start one: see Embedding for the three integration paths (redirect link, inline iframe, JavaScript SDK) โ each handles session creation differently.
6. Consent Token
A Consent Token is the receipt the user produces by completing a Flow. It's the cryptographic answer to "did this user actually click through and consent to these terms?" โ your backend verifies it, and only then trusts that consent has been granted. Like ยง5, this concept applies specifically to Flow-shaped artifacts; other surfaces produce different receipts (see below).
Properties:
- Single-use. Verifying the token clears it server-side; replays return
expired. - Time-limited. 5-minute lifetime from issuance. Verify promptly.
- Opaque. A 32-byte URL-safe base64 string. Don't parse it client-side; treat it as a bearer.
- Browser-issued. Delivered to your page via
postMessageasevent.data.consent_tokenon theoverturo:completeevent.
Always verify server-side. Verification returns valid: true plus a status โ one of nine values. The two states you act on are fulfilled (proceed) and anything else (don't):
Status values
| Status | Group | Meaning |
|---|---|---|
| fulfilled | Terminal โ success | User completed the flow. This is the only status you should treat as "consented." |
| expired | Terminal โ failure | Past the 5-minute TTL, or already consumed by an earlier verify. Do not trust. |
| revoked | Terminal โ withdrawn | User later withdrew consent. Stop the data use this consent authorised. |
| pending | In-flight | Session opened, user has not begun. |
| authenticating | In-flight | User is signing in. |
| verifying | In-flight | Identity / credential checks in progress. |
| reviewing | In-flight | User is reading the consent terms. |
| informed | In-flight (informed-consent path) | User has been presented with the disclosures required by the flow. |
| evidenced | In-flight (informed-consent path) | Evidence captured (e.g., comprehension check, signature). One step before fulfilled. |
Two lifecycle paths โ standard (pending โ authenticating โ verifying โ reviewing โ fulfilled) and informed-consent (pending โ informed โ evidenced โ fulfilled) โ converge on fulfilled on success or expired / revoked on failure.
Verify with: Ruby SDK (client.consent.consent_sessions.verify(token)) or the equivalent REST call from any backend.
Don't trust the browser. The browser-side overturo:complete event is your prompt to verify, not your proof of consent. A motivated attacker can forge any postMessage. The consent token itself can only be issued by Overturo and only verified once โ that's where the trust comes from.
Other surface artifacts
Consent Tokens are the integration model for Flows. Other surfaces produce different artifacts because the underlying decision shape differs โ multi-party signing, ongoing negotiation, agent authority. If you're integrating one of those, look here:
-
Agreements (multi-party โ e.g. peer-to-peer mutual data sharing, quorum-of-N decisions). Lifecycle:
pending โ partially_signed โ active. The receipt is the set of signatures, not a single token; the Agreement becomes binding when the required quorum is reached. -
Authorities (a user grants bounded authority to an AI agent). The artifact is an AI Agent Token (
ra_agent_*) obtained via OAuth token exchange โ not a Consent Token. The user's "yes" is the Authority record; the agent token is what the AI software holds and presents on each call. Since the authority-receipts work, that "yes" also freezes a durable authorization record the principal and operator can download and dispute with โ see Docs → Receipts. -
Data deals (some Policy types โ data licenses, data offers, bounties, broadcasts). Lifecycle:
draft โ offered โ countered โ user_revised โ accepted / declined / expired. The "receipt" is the deal-acceptance state on the Policy itself, not a one-shot token; multiple counter-offers may happen before acceptance.
See the Surfaces tab for the surface-by-surface picture of which artifact applies where.
7. AI Agent Token
An AI Agent Token (prefixed ra_agent_*) is a credential issued to an AI agent acting on a user's behalf. Unlike a user's API token, it carries explicit authority bounds โ scope, action, value, time, escalation, reputation โ that the user set when delegating.
See: Authentication ยง4 for the issuance flow and the bounds reference.
8. Delegation Grant
A Delegation Grant (format dlg_<region>_<hashid>) lets one human act on another's behalf โ guardian for a minor, caregiver for a patient, legal POA, IT admin for an employee, peer for a friend. The delegate authenticates as themselves and includes the grant ID in the X-Delegation-Grant-Id header alongside their own token.
Different from an agent token: agent tokens are for AI software, delegation grants are for human-to-human authority. See: Authentication ยง5.
9. Audit Trail
Every consent decision โ granted, denied, modified, revoked โ is recorded in a tamper-proof audit trail. Each record is hash-chained to the one before it, so a single after-the-fact edit invalidates every later record's signature. The chain head is published to an external witness every fifteen minutes, which means even Overturo can't rewrite history retroactively without producing visible evidence.
Three guarantees this gives you:
- Non-repudiation. A consent that was granted can be proven granted at a specific time, and a denial can be proven denied โ no matter who later disputes it.
- Witness trail. If the consent record changes, the cryptographic chain breaks; an external auditor can detect tampering without trusting Overturo.
- Discoverable. The full history of any consent โ every state transition, every actor, every signature โ is queryable from your application's audit endpoints.
Practical implication: when a regulator, partner, or end-user asks "did this user actually consent?", the audit trail is the answer of record. Don't keep your own duplicate log unless you have a compliance reason โ the canonical trail is here.
Cross-border & residency
Every person's data is stored in their own country and stays there. When an integration spans countries, Overturo doesn't move the data โ it moves the smallest possible proof of a fact across the border. The substance stays home; the trust travels.
Two different things can cross a border:
- Minimized status โ an opaque identifier, a status, a count, or a person's display initials. Never personal data. This is how a consent or a signature is known elsewhere without the record leaving its country.
- A recorded transfer โ when personal data genuinely must cross, it is written to the tamper-proof transfer register with the lawful basis it relied on. A transfer basis is a distinct legal concept from the consent/processing basis.
A resource is addressed in the country where it's homed; calling the wrong country returns an explicit error telling you which country to call instead. The exact error contract is documented on the Authentication tab.
Honest boundary: data stays in its country today via per-country storage; the physical single-country guarantee strengthens as dedicated in-country infrastructure rolls out. We say which is which rather than imply the physical guarantee is already complete.
Surface Contract Specs
Overturo is organized into seven product surfaces. Each surface provides a focused set of APIs and capabilities for a specific aspect of personal data management.
Shared delivery layer: all seven surfaces share a single Trust Surface delivery layer for the moments when they need to ask a principal to make a decision — consenting to data sharing (Connect / Consent), approving a delegate action (Delegate / Comply), signing an attestation (Verify / Agree). The chrome, branding, deadline, SDK, and webhook contract are uniform across surfaces. See the Embedding tab.
Connect
Application registration and identity linkage.
- Application configuration
- Webhook configuration
- Trust pairings
- Device linking
- Environment management
Verify
Credential issuance, verification, and trust scoring.
- W3C Verifiable Credentials
- SD-JWT selective disclosure
- Present-anywhere verification (issue in country, verify anywhere)
- Trust scores
- Vouch-based reputation
Protect
Data policies, access controls, and the data economy.
- Data policy creation
- DSAR management
- Data licensing
- Data offers and RFOs
- Secure transfers
Agree
Multi-party agreements and digital signatures.
- Multi-party agreements
- Cross-border co-signing (data stays home)
- Quorum voting
- Collaborative workspaces
- Agreement templates
Delegate
Authority delegation to AI agents and third parties.
- Delegation grants
- AI agent registration
- Bounded authority (7 types)
- Jurisdiction-bounded agents (refuse out-of-bound crossings)
- Durable authorization receipts + declared disclosure receipts
- Emergency access
- Security event streaming
/delegations
/agent_identities
/application_agents
/authorities
/authorization_receipts
/disclosure_receipts
Open surface →
Comply
Regulatory compliance and cross-border evidence.
- Cross-border transfer register
- Adequacy verdict
- Cross-border data access (DSAR)
- Proof of Residency
- Pluggable compliance modules
- Governance frameworks
- Evidence packages
- Clean rooms
- External witnessing
/transfer_register
/residency_posture
/modules
/governance
/evidence_packages
/clean_rooms
Open surface →
Unified Trust Inbox
A single, per-principal list of pending decisions covering both consent requests and approval requests. Lives at /inbox.
/inbox
Cross-surface aggregation. Pending consent flows (from the Consent surface) and pending approval requests (from the Delegate surface) are unioned into one filterable, paginated list. The principal navigates from any row to the canonical per-decision URL at /decisions/:session_token.
- Consent + approval rows in one list, filterable by kind
- Trigger-type filter for approvals (scope, action, value, time, escalation, reputation)
- Application-scoped narrowing via
?application_id=tp_dev_โฆ - Deadline-ascending or newest-first sort
- Auto-submitting Stimulus filter UI; CSP-clean
/inbox
/decisions/:session_token
Migration note: the legacy /oap/escalations operator inbox URL has been retired. Per-row URLs (/oap/escalations/:id) and the JSON API namespace under /api/v1/oap/escalations remain unchanged.
Authentication Guide
Five authentication mechanisms, each suited to different integration scenarios.
At a Glance
| Method | Use Case | Header |
|---|---|---|
| API Token | Server-to-server REST API calls | Authorization: Bearer <token> |
| OAuth 2.0 / OIDC | User-facing apps needing delegated access | Authorization: Bearer <access_token> |
| Publishable Key | Client-side SDK and embed integrations | X-Publishable-Key: pk_live_* |
| AI Agent Token | AI agents acting on behalf of users | Authorization: Bearer ra_agent_* |
| Delegation Grant | Human delegates acting on behalf of another user | X-Delegation-Grant-Id: <grant_id> |
1. API Tokens
API tokens provide simple bearer authentication for server-to-server calls. Create tokens from Credentials.
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
https://overturo.com/api/v1/me
Security: API tokens grant full account-level access. Never expose them in client-side code, public repositories, or browser requests. Use publishable keys for client-side integrations instead.
2. OAuth 2.0 / OIDC
For applications that need to act on behalf of users, use the OAuth 2.0 Authorization Code flow with PKCE. Overturo is an OpenID Connect provider.
Step 1: Redirect the user to the authorization endpoint:
GET /oauth/authorize?
client_id=YOUR_CLIENT_ID&
response_type=code&
redirect_uri=YOUR_REDIRECT_URI&
scope=openid+profile+email&
state=RANDOM_STATE
Step 2: Exchange the authorization code for tokens:
curl -X POST https://overturo.com/oauth/token \
-d grant_type=authorization_code \
-d code=AUTHORIZATION_CODE \
-d redirect_uri=YOUR_REDIRECT_URI \
-d client_id=YOUR_CLIENT_ID \
-d client_secret=YOUR_CLIENT_SECRET
Step 3: Use the access token for API calls:
curl -H "Authorization: Bearer ACCESS_TOKEN" \
https://overturo.com/api/v1/me
Available Scopes
| Scope | Description |
|---|---|
| openid | OpenID Connect identity token |
| profile | User profile information (name, picture) |
| User email address | |
| offline_access | Refresh token for long-lived sessions |
| audit:verify | API-token scope: read authorization receipts (GET /api/v1/authorization_receipts/:id) |
| disclosures:write | API-token scope: record declared agent disclosures (POST /api/v1/disclosure_receipts) |
Additional Grant Types
- Device Authorization (RFC 8628) โ for devices without a browser
- Token Exchange (RFC 8693) โ for AI agent delegation flows
- Pushed Authorization Requests (RFC 9126) โ for secure parameter transmission
3. Publishable Keys
Publishable keys (prefixed pk_live_ or pk_test_) are safe for use in client-side code. They identify your application but cannot access sensitive data.
import { Overturo } from "@overturo/js"
const overturo = new Overturo({
publishableKey: "pk_live_abc123"
})
// Or via HTTP header:
// X-Publishable-Key: pk_live_abc123
Allowed Origins
Browser-based SDK calls and embedded consent flows for an application can only originate from URLs registered in that application's allowed origins. Server-side calls (Bearer tokens) are unaffected.
Add an origin via the dashboard: Connect โ choose your application โ Allowed Origins tab โ enter https://yourdomain.com โ Add.
Add via the API:
curl -X POST \
-H 'Authorization: Bearer YOUR_API_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"allowed_origin":{"origin":"https://yourdomain.com"}}' \
https://overturo.com/api/v1/applications/YOUR_APP_ID/allowed_origins
Debugging "CORS preflight failed": the application's Activity tab shows
cors.preflight_denied
events for any origin that hit a preflight without being registered.
Allowed Origins vs embed_origin:
Allowed origins is a per-application registration (set once, applies to all sessions).
embed_origin
is the per-session URL the SDK was initialized on (the iframe host). Both must agree โ
a session can't be created with an embed_origin that isn't in the application's allowed origins.
Allowed Origins vs redirect_uri:
these are independent registrations. Redirect URI controls where OAuth callbacks may land
(full URL with path, server-side check). Allowed Origins controls where browser-direct API calls
and iframes may originate (origin only). Editing one does not edit the other.
4. AI Agent Tokens
AI agents authenticate using agent tokens (prefixed ra_agent_) obtained through the token exchange flow. These tokens carry bounded authority constraints.
- Register an AI agent identity via the Delegate API
- The user grants a delegation with specific authority bounds
- Exchange the delegation grant for an agent token via
/oauth/token/exchange - Use the agent token for API calls, including the MCP gateway
curl -H "Authorization: Bearer ra_agent_abc123..." \
https://overturo.com/mcp/v1/agent
Wiring MCP into Claude Desktop, Cursor, or VS Code? MCP Launchpad pre-fills the config for you.
Authority Bounds
| Bound | Description |
|---|---|
| Scope | Which API scopes the agent can access |
| Action | Specific operations allowed (read, write, delete) |
| Value | Maximum monetary value per transaction |
| Time | Token validity window (expiry time) |
| Escalation | Whether the agent can request additional permissions |
| Reputation | Minimum trust score required for the agent |
5. Delegation Grants
Delegation grants allow a human delegate (guardian, caregiver, authorized representative) to make API calls on behalf of another user.
- The principal creates a delegation grant specifying the delegate and allowed scopes
- The delegate authenticates normally with their own credentials
- The delegate includes the
X-Delegation-Grant-Idheader alongside theirAuthorizationheader - The API processes the request in the context of the principal
curl -H "Authorization: Bearer DELEGATE_USER_TOKEN" \
-H "X-Delegation-Grant-Id: dlg_dev_abc123" \
https://overturo.com/api/v1/me
Delegation Types
| Type | Description |
|---|---|
| Guardian | Legal guardian acting for a minor or dependent |
| Caregiver | Caregiver managing data on behalf of a care recipient |
| Power of Attorney | Legally authorized representative |
| IT Admin | Organizational administrator managing user accounts |
| Peer | Trusted peer with limited delegated access |
Key difference from AI Agent Tokens: Delegation grants are for human-to-human delegation. The delegate authenticates as themselves and the grant establishes who they are acting for. AI agents use their own ra_agent_* token instead.
Country-aware addressing
Each person's records are homed in their own country, and the API serves a resource from the country where it's homed. You don't choose the country per request โ it follows the subject. The home country is reported as an ISO country code (for example IT).
If you address a resource through the wrong country, the API returns an explicit error carrying a home_country field that tells you which country to re-issue the request against. It never leaks the resource or its data โ a "wrong door" signal, not a failure.
The exact error code and JSON shape are in the OpenAPI reference.
OAuth Authorization
Pushed Authorization Request (PAR) โ required for every authorization flow. Sits alongside the REST API but follows OAuth 2.0 conventions rather than RESTful CRUD.
Endpoint
/oauth2/par
PAR is required for all OAuth clients
(require_par: true is the platform-wide default).
Push your authorization parameters server-to-server, then redirect the user to
/oauth/authorize with only
client_id and request_uri.
Discovery
Standard OIDC discovery endpoints live under
/.well-known/* โ see the
Authentication
tab.
The discovery document also advertises five OIDC-A extension fields
(agent_claims_supported,
agent_types_supported,
delegation_methods_supported,
oap_jwks_uri,
oap_protocol_versions_supported). See the
AI Agent Authorization โ OIDC-A Interop
surface for the full schema.
API Reference
The Overturo API is organized around REST. All requests and responses use JSON.
Decisions API โ canonical session-mint endpoint
POST /api/v1/decisions is the canonical endpoint for creating any Trust Surface session. It supersedes POST /api/v1/consent_sessions (now a deprecated alias).
Request body carries a closed-enum kind discriminator: "consent" or "approval". Response is kind-uniform โ the session_token resolves to a kind-specific Decision under the hood. See the Embedding tab for the full walkthrough.
The closed error-code reference for /api/v1/decisions/* is drawn from TrustSurface::Errors::REASON_CODES and visible in the OpenAPI explorer.
OpenAPI Reference: OpenAPI Reference · openapi.json
Base URL
https://overturo.com/api/v1/
Authentication
The API supports multiple authentication mechanisms. See the Authentication tab for full details.
Authorization: Bearer YOUR_API_TOKEN
Response Format
All responses are returned as JSON. Successful responses include the resource or collection directly. Errors include a message field.
{
"id": "app_abc123",
"name": "My Application",
"environment": "sandbox",
"created_at": "2025-01-15T10:30:00Z"
}
Error Codes
| Code | Meaning |
|---|---|
| 400 | Bad Request โ invalid parameters or malformed JSON |
| 401 | Unauthorized โ missing or invalid authentication |
| 403 | Forbidden โ authenticated but insufficient permissions |
| 404 | Not Found โ resource does not exist or is not accessible |
| 422 | Unprocessable Entity โ validation errors on the submitted data |
| 500 | Internal Server Error โ something went wrong on our end |
Endpoints by Surface
Working with AI agents? The AI Agent Authorization surface covers OAP end to end โ Conductor and Oversight modes, receipts and verification, SDKs, and the OIDC-A 1.0 ID Token acceptance pipeline at OAP for Agents โ Grant creation.
Well-Known Endpoints
Discovery endpoints following standard specifications. Receipt-specific machine endpoints (the published receipt schema and export flavors) are documented on the Receipts tab.
| Endpoint | Purpose |
|---|---|
| /.well-known/openapi | REST API schema (OpenAPI 3.1) |
| /.well-known/api-catalog | RFC 9727 catalog of API descriptions on this host |
| /.well-known/openid-configuration | OAuth 2.0 / OpenID Connect authorization server discovery |
| /.well-known/jwks.json | JSON Web Key Set for authorization server token verification |
| /.well-known/did.json | DID document for decentralized identity |
| /.well-known/openid-credential-issuer | Verifiable credential issuer metadata |
| /.well-known/credential-status/:id | Verifiable credential status list (by status list id) |
| /.well-known/oap-attestation | OAP agent attestation provider discovery |
| /.well-known/oap-credentials/:id | OAP agent credential metadata (by credential id) |
| /.well-known/oap-revocation-list | OAP token revocation list |
| /.well-known/ssf-configuration | Shared Signals Framework configuration |
| /.well-known/mcp-configuration | Model Context Protocol discovery |
| /.well-known/witness-configuration | External witness anchoring configuration and receipt signing-key discovery (current + historical region keys) |
| /.well-known/transparency/:application_id | Controller identification record for an application's receipts: declared parties, export flavors, and the schema, interpretation, and key-discovery pointers |
| /.well-known/transparency/agents/:agent_id | Agent accountability record: the operator's declared identity, the agent's platform-attested status, current credential attestations, and the receipt machine pointers |
| /.well-known/transparency/agents/:agent_id/manifests/:version | A published capability-manifest version: the agent's declared capability set frozen at pin time, with its content hash for independent verification |
| /surfaces/developer/mcp | Pre-configured client config snippets and a disposable-agent generator. |
Cross-border endpoints
Read and prove the cross-border story. All are scoped to your application and carry no personal data unless noted. Full reference is in the OpenAPI document.
| Method & path | What it returns |
|---|---|
| GET /api/v1/transfer_register | Cross-border transfers, each with its lawful basis and adequacy verdict. |
| POST /api/v1/transfer_register/evidence_package | A signed, independently-verifiable evidence package of the register. |
| GET /api/v1/residency_posture | Where your subjects' data is stored, per country, with an assurance status. |
| POST /api/v1/residency_posture/evidence_package | A signed Proof of Residency โ proves data stayed in-country. |
| POST /api/v1/presentations | Verify a credential presented from another country, against the issuing country's published key, with no callback. |
A subject's right of access / portability is fulfilled across countries through the self-service data export. The signing decision kind rides the Decisions API โ see the Embedding tab.
Embedding a Decision
Four delivery modes (embed, popup, redirect, direct) for both kinds of Decision (consent, approval). Pick the simplest that meets your needs โ you can always upgrade later. The same <overturo-decision> element and the same postMessage protocol handle both kinds.
Keeping your own form? The Forms documentation covers the Overturo field, the capture snippet, Typeform and the Record API.
Unified surface URL family
The canonical participant URL is /decisions/:session_token (and /decisions/:session_token/embed for iframes). All Decision kinds โ consent today; approval, signature, verification in future โ mount on the same routes. The legacy /accord/:id and /oap/escalations/... URL families have been retired in favor of the unified URL family.
New: /api/v1/decisions/* session-mint API
The matching API endpoint family is now /api/v1/decisions with a closed-enum kind discriminator. Both URL families share session tokens โ a session minted via either URL resolves under either. The legacy /api/v1/consent_sessions/* endpoints continue to work as a thin alias subclass and emit RFC 8594 Deprecation + Sunset headers. See the OpenAPI reference for the full endpoint contract.
Before you read on โ what are you trying to do?
| Goal | Where to go |
|---|---|
| Authenticate users only | Your application is a standards OAuth 2.0 / OIDC client. Any compliant library works โ you do not need the JavaScript SDK. Open the Connect surface, pick your Application, open the Integration tab, and expand Authentication only for pre-filled snippets (Node, Python, browser SPA). |
| Show a consent UI | The user has to see and accept a Flow before you can use their data. Read on. |
At a Glance
| Approach | Snippet | JS required? | Callbacks | Use when |
|---|---|---|---|---|
| Redirect link | <a href=โฆ> | No | Webhook (server-side) | Simplest path. User leaves your site, completes the Flow, returns to a callback URL. Token arrives via webhook. |
| Inline iframe | <iframe src=โฆ> | Optional (for events) | postMessage |
Show the Flow inside your page; user never leaves. |
| JavaScript SDK | @overturo/js | Yes (npm) | Function callbacks | Multi-step app integration; popup mode; programmatic embed creation. |
1. Choosing your integration
Start at the top and stop at the first "yes":
- Is a plain link acceptable โ user clicks, navigates to Overturo, completes the Flow, returns to your site? โ Redirect link. One line of HTML, no JavaScript.
- Do you want the Flow to render inside your page without the user navigating away? โ Inline iframe. One line of HTML; add a
postMessagelistener if you need to react to the consent result. - Do you need callbacks (success / cancel / error), popup mode, dynamic Flow IDs, or anything beyond a static embed? โ JavaScript SDK.
npm install @overturo/js+ a few lines of code.
All three approaches reach the same Flow. The choice is purely about how much surrounding JavaScript you're willing to write โ not about which features you can use.
2. Prerequisite: activate your Flow first
Embedding works only against active Flows. A Flow in draft status will return 404 from the Flow URL and reject session creation. Activation is one click in the Flow's editor โ and once activated, the Flow's terms are immutable, so do it after you've confirmed the field set, retention, and presentation are correct.
Where to activate: on the Consent surface, open your Flow โ click Activate. The Flow's Embed tab unlocks at that point and starts showing copy-paste snippets pre-baked with your Flow's URL and publishable key โ see ยง6 below.
2.5 Prerequisite: register your origin
Browser-based integrations (ยง4 iframe, ยง5 SDK) load resources and call API endpoints from your site's origin. The Application's CORS allowlist must include that origin or the browser blocks the request โ typically with no error visible inside the Flow, just a failed preflight in the network tab. Server-side calls authenticated with a Bearer token are unaffected.
Where to register: Connect surface โ your Application โ Allowed Origins tab. Add the exact origin (e.g. https://yourdomain.com) โ no path, no query string, no trailing slash.
- Production Applications require HTTPS.
- Development Applications also accept
http://localhostandhttp://127.0.0.1(any port). - The tab shows the last preflight timestamp per origin so you can confirm a registration is taking effect.
3. Redirect link
The simplest possible integration: a plain <a> tag pointing at the Flow's hosted page. Zero JavaScript. The user clicks, walks through the Flow on a page hosted by Overturo, then returns to your redirect_uri.
<a href="https://trust.overturo.com/accord/FLOW_ID?redirect_uri=https://yoursite.com/consented">
Give Consent
</a>
Substitute FLOW_ID with your Flow's ID (format flw_dev_โฆ in development, flw_us_โฆ / flw_eu_โฆ in production โ see Concepts ยง4).
How completion is signalled
Redirect mode signals completion in two channels โ the URL the user lands on, and a webhook your backend receives. The trust signal is the webhook; the URL is for UX.
-
On success โ the user lands at your
redirect_uriwith no query parameters appended. Do not infer consent from the bare arrival; an attacker can hit your callback URL directly. -
On decline โ the user lands at
redirect_uri?error=access_denied. Use this to render a "you can try again" page; do not treat absence oferroras proof of consent. -
The Consent Token arrives via webhook โ when the user completes the Flow, your Application's webhook endpoint receives a
consent.grantedevent carrying the consent token, the user identifier, and the Flow ID. That's the trust boundary. Verify the webhook signature, look up the user, and treat consent as granted from that point.
Wiring webhooks: see the Ruby SDK's webhook handler reference (search for "Webhooks") โ the same payload shape applies regardless of the language receiving it. Webhook endpoints are configured per-Application on Credentials โ Webhooks.
The redirect URL is for UX, not trust. Pure-redirect mode is the simplest way to route the user, but the only authoritative signal that consent was granted is the webhook (or, equivalently, querying the API for that user's fulfilled consent on the Flow). If your integration needs a token in the page that handles the redirect, use the inline iframe or JavaScript SDK instead โ both deliver the Consent Token directly to your client code via postMessage.
4. Inline iframe
Render the Flow inside an iframe on your page. The user never navigates away. You receive completion events via window.postMessage.
<iframe
src="https://trust.overturo.com/accord/FLOW_ID"
sandbox="allow-forms allow-scripts allow-same-origin"
style="width: 100%; height: 600px; border: none;"
allow="publickey-credentials-get *">
</iframe>
The sandbox attribute is required and must include allow-forms allow-scripts allow-same-origin at minimum. The allow="publickey-credentials-get" permission is needed for passkey-based authentication inside the Flow.
The hosting page's origin must be on the Application's CORS allowlist or the browser will block the iframe's API calls โ see ยง2.5.
Receiving events
The iframe broadcasts events on the parent window โ completion, cancellation, resize, ready. The handler shape is the same one the JavaScript SDK uses internally; the JavaScript SDK reference documents the full event vocabulary (look for "Handle Consent Events"). Always verify event.origin === "https://overturo.com" before processing โ the postMessage you receive is your prompt to verify the consent token server-side, not your proof of consent.
5. JavaScript SDK
For everything beyond a static embed โ function callbacks, popup mode, programmatic Flow IDs, dynamic configuration โ install @overturo/js and call overturo.consent({ flowId, container, onComplete }). The SDK handles session creation, iframe lifecycle, postMessage routing, and origin validation for you.
Three things the SDK gives you that the raw iframe doesn't:
- Popup mode.
overturo.popup(...)opens the Flow in a separate window and posts the result back when the user finishes โ useful when iframe sizing is awkward. - Function callbacks.
onComplete,onCancel,onError,onResize,onStepโ typed callbacks instead of message-event dispatch. - Typed errors.
OverturoApiErrorexposesstatusCodeand the response body โ easier to branch on than a genericfetchfailure.
The hosting page's origin must be on the Application's CORS allowlist or every SDK call from the browser will fail preflight โ see ยง2.5.
Full reference: JavaScript SDK โ installation, complete examples, and error handling.
6. Per-Flow snippets
Each Flow has its own Embed tab in the Consent surface that generates copy-paste-ready snippets pre-baked with the Flow's actual URL, publishable key, and any sandbox / postMessage configuration you've set on the Flow. The snippets there are the same shapes documented above; the difference is they have your real values filled in.
The per-Flow Embed tab also lets you configure things this developer-portal page intentionally doesn't cover โ sandbox flag tuning, allowed parent origins for postMessage, embed styling, session-token issuance, and embed analytics. Those are operational concerns scoped to a specific Flow; this tab is the conceptual reference for what's possible across all Flows.
Delivery modes
The per-Flow Embed tab generates two HTML-shape snippets, and popup is an SDK-managed shape:
redirectโ same shape as ยง3 above, with your Flow's URL filled in.iframeโ same shape as ยง4 above, with your Flow's URL and any sandbox-flag overrides you've set.popupโ SDK-managed (window.open, postMessage, focus restoration), not a static HTML snippet. Use the JavaScript SDK'soverturo.popup(...)method.
How to find the per-Flow Embed tab: Consent surface โ open your Flow โ Embed tab. Available once the Flow is active.
Or: start from the Application instead of the Flow
This per-Flow Embed tab generates snippets for one Flow across any host. There is a complementary per-Application configurator that generates snippets for one Application across any of its Flows โ useful when several Flows live under the same Application and you want the publishable key already filled in.
How to find it: Connect surface โ open your Application โ Integration tab. The tab includes a Flow picker, three runnable integration patterns (embed / popup / redirect), and an Authentication-only section for the standards-OAuth path (see the gate at the top of this page).
The signing decision kind
Cross-border signing is delivered as a third Decision kind โ signature_request โ alongside consent and approval. The kind is inferred server-side from the session token, so the <overturo-decision> markup is unchanged: you mount it exactly as you do for consent.
A signing decision resolves to one of four outcomes โ signed, declined, expired, countered โ surfaced on the complete event and the matching signature.* webhooks. Signing decision state also carries the signer's home country (an ISO country code) and the signature assurance level.
Client SDK support for the signing kind is coming. The signature_request kind, the signature.* outcomes, and the residency/assurance metadata are being added to overturo-js. The server capability is live today; until the client ships, drive the signing kind through the Decisions API directly.
7. postMessage protocol v2
Embedded Flows post messages to your host page through window.postMessage. Every envelope carries six required fields and conforms to a published JSON Schema (Draft 2020-12). The JavaScript SDK validates inbound messages automatically; manual listeners should validate too.
Envelope
{
"type": "overturo:state", // closed-enum, see below
"kind": "consent", // "consent" | "approval"
"version": 2, // always 2 for this protocol
"payload": { ... }, // per-kind-per-type schema
"ts": "2026-06-05T12:00:00.000Z", // ISO-8601 UTC, server-authoritative
"message_id": "550e8400-..." // UUID v4, unique per emission
}
Message types (closed enum โ 7)
| Type | Direction | When it fires | Payload shape (consent / approval) |
|---|---|---|---|
overturo:ready | frame โ host | Iframe finished loading | kind, session_token, deadline_at, branding, mode, locale |
overturo:state | frame โ host | State transition | from, to, step_index, total_steps (consent) / kind_payload (approval) |
overturo:complete | frame โ host | User finished | outcome ("granted"/"declined" or "approved"/"denied"), exchange_token, completed_at |
overturo:cancel | bidirectional | User declines or host cancels | reason, cancelled_by |
overturo:error | frame โ host | Error in the flow | code, message, fatal, recoverable_via |
overturo:resize | frame โ host | Content height changed | height (integer pixels) |
overturo:deadline | frame โ host | T-30s / T-15s / T-5s / T-0 | remaining_seconds, level ("warning" / "expired"), deadline_at |
Host-side listener (manual integration)
If you're not using the JavaScript SDK, attach a listener and check the envelope yourself. Always validate event.origin against the Overturo host before trusting the payload.
window.addEventListener("message", function (event) {
// 1. Validate origin
if (event.origin !== "https://overturo.com") return;
// 2. Validate envelope shape
var env = event.data;
if (!env || env.version !== 2) return;
if (typeof env.type !== "string" || !env.type.startsWith("overturo:")) return;
// 3. Route on type + kind
switch (env.type) {
case "overturo:ready":
console.log("[overturo] ready", env.kind, env.payload);
break;
case "overturo:state":
console.log("[overturo] state", env.kind, env.payload.to);
break;
case "overturo:complete":
// exchange_token is exchanged via POST /api/v1/decisions/:session_token/exchange
handleDecisionComplete(env.kind, env.payload);
break;
case "overturo:cancel":
console.log("[overturo] cancelled", env.payload.reason);
break;
case "overturo:error":
console.error("[overturo] error", env.payload.code, env.payload.message);
break;
case "overturo:deadline":
console.warn("[overturo] " + env.payload.remaining_seconds + "s remaining");
break;
case "overturo:resize":
iframe.style.height = env.payload.height + "px";
break;
}
});
JSON Schemas (normative)
Every envelope and every payload has a JSON Schema. They ship in the SDK at lib/sdk/overturo-js/schemas/postmessage-v2/:
envelope.jsonโ top-level six-field envelopeready.json,resize.json,cancel.jsonโ kind-agnostic payload schemasconsent/{state,complete,error,deadline}.jsonโ consent-specific payloadsapproval/{state,complete,error,deadline}.jsonโ approval-specific payloads
Kind discriminator
The kind field is required on every envelope. consent covers user-facing consent flows; approval covers AI-agent action approvals. Both share the envelope, the seven message types, and the lifecycle vocabulary; only the per-payload semantics differ.
Origin invariant
The frame's postMessage call never uses "*" as the target origin. If your Application's allowed origin isn't configured, the frame drops messages and increments a trust_surface.postmessage_dropped_total counter on the server with reason=origin_missing. Configure your allowed origins on the Application's Connect tab.
Consent in your own forms
You keep your sign-up, lead, intake, booking or checkout form. Overturo becomes the consent system of record behind the checkbox: the versioned wording the person read, a signed receipt they hold, a withdrawal path that reaches your systems, and the audit evidence that demonstrates consent.
What the record proves
Overturo proves when the decision was recorded, which wording was in force, who attested it and that nothing has changed since; for a record an organisation captured in its own form, Overturo does not vouch that the control was shown unticked; for a record captured through the Overturo field, Overturo rendered the control, showed it unticked and recorded the person's own change, and does not vouch for what the page placed around it.
Every receipt names where the decision was captured and who attested it: an Overturo-rendered field inside your form, your form committed by your server, your form committed by the browser, a form platform's signed webhook, your server directly, or an import of records you already held.
Four ways in
| Path | You need | Receipt says |
|---|---|---|
| Your own form + the Overturo field | Two script tags, one element where the checkbox used to be, and a server that commits the capture token | rendered by Overturo inside your form, shown unticked, committed by you |
| Your own form + the snippet | One script tag, a map from purposes to inputs, and a server that commits the capture token | captured in your form, committed by you |
| Typeform | A connected form, a field mapping, Typeform's signed webhook | captured in a Typeform form, delivered by its signed webhook |
| Your server (Record API, import) | Your application credential | attested by you; imported records keep your original capture time |
Want Overturo's page instead of your form? See Embedding.
Which flows can be captured
A flow can be captured when its consent needs nothing but a decision: no verification, data-collection or bank step, and a declared consent duration (what bounds how long the person's details are kept for a consent they never claimed an account for). A capture carries decisions and how they were presented, never collected data fields. The flow page's Records tab shows the verdict.
What a capture may attest
For every purpose the flow resolves, a capture states the decision and the presentation it was made under (shown ticked by default, mandatory). The same rules that govern Overturo's own decision screens apply here: a pre-ticked or mandatory box is never consent.
| Purpose | Accepted attestation | Recorded as |
|---|---|---|
| Consent (explicit or informed) | A decision, not default-ticked, not mandatory | granted or declined; an unticked box is a decline, never silence |
| Consent, any mechanism | Default-ticked or mandatory | refused, with the purpose named |
| Legitimate interest with the right to object | A decision; default-ticked allowed | ticked โ noticed; unticked โ an objection on the transparency register |
| Notice-only (contract, legal obligation) | No decision | noticed |
A purpose the flow does not resolve, or a missing consent purpose, is refused by name. Your terms-of-service acceptance is a contract purpose, not a consent checkbox.
1. Your own form
Newsletter, lead, checkout, promotion, loyalty, messaging and research opt-ins have ready-made flows under the Opt-ins solution; every one of them is capturable here.
The Overturo field: the control rendered by Overturo, inside your form
Place one element where the checkbox used to be. Overturo renders the published wording for the flow, shows the control unticked, and observes the person's change in a frame your page cannot script. At submit the field captures from its own frame and hands your form the capture token; your server commits it exactly as for the snippet. The receipt says the control was rendered by Overturo, which notice version was shown, and how visible the control was when the form was submitted. Choose the field when the receipt must prove how the control was shown; choose the snippet below when you must keep your own markup.
<!-- Two scripts: the core and the field entry (loaded explicitly, never fetched behind your back) -->
<script src="https://overturo.com/sdk/v1/overturo.js"></script>
<script src="https://overturo.com/sdk/v1/overturo-field.js"></script>
<form id="signup-form" action="/signup" method="post">
<input id="email" name="email" type="email" required>
<!-- Overturo renders the consent control here: the published wording, shown unticked, in your form -->
<overturo-consent-field name="consent" flow="flw_โฆ" locale="en"
appearance='{"fontFamily":"system","accent":"#2563eb","radius":"6px","density":"regular"}'></overturo-consent-field>
<label><input name="terms" type="checkbox" required> I accept the terms</label>
<button type="submit">Create account</button>
</form>
const overturo = new Overturo({ publishableKey: "pk_live_โฆ" })
// The field is a form control: at submit it captures from its own frame and
// hands the capture token to your form (a hidden input and the element's value).
overturo.forms.attach({
flowId: "flw_โฆ",
form: "#signup-form",
field: "overturo-consent-field", // the element inside the form (one per flow)
subjectField: "#email",
externalIdField: "#customer_id", // optional: your own identifier
onCaptured: (r) => console.log("capture token", r.token),
onError: (e) => console.warn("capture failed; the form submitted anyway", e.message),
})
// Then, on your server, commit the token with your application credential โ
// exactly as for the snippet. The receipt says the control was rendered by Overturo.
The field renders every purpose of the flow that takes a decision (name a subset with purposes="โฆ", but a consent purpose can never be left out), notice-only purposes as text, and a right-to-object purpose ticked with the objection wording. It renders no required control: your terms-of-service checkbox stays your own. One field per flow per form. The application needs at least one allowed origin.
Appearance. A bounded vocabulary, applied inside the frame and never sent to Overturo: fontFamily (system, inter, serif, mono), fontSize, text, muted, accent, border, radius, density (compact, regular, relaxed); plus theme and locale attributes. An invalid value is ignored. A small "Recorded by Overturo" mark is always shown, so the person knows who holds their receipt.
The field needs no cookie: a signed render proof, known only inside the frame, authenticates the capture, so it works where third-party cookies are blocked. The host page learns nothing but the state of the controls and the capture token.
The snippet: your own control, captured at submit
At submit, before the form leaves the page, the snippet reads each mapped control (ticked or not, shown ticked by default or not, required or not), builds a sanitised snapshot (consent controls and labels kept, every other input value stripped) and posts one capture with your publishable key. It receives a capture token, writes it into a hidden input named overturo_capture_token, and lets the submission proceed. If Overturo is unreachable, the form still submits and the error event fires.
import { Overturo } from "@overturo/js"
const overturo = new Overturo({ publishableKey: "pk_live_โฆ" })
// Your existing form keeps every input. Map each consent control to its purpose,
// name the email field, and let the snippet capture the decision at submit.
overturo.forms.attach({
flowId: "flw_โฆ",
form: "#signup-form",
map: { marketing: "#marketing_opt_in", analytics: "#analytics_opt_in" },
subjectField: "#email",
externalIdField: "#customer_id", // optional: your own identifier
labels: { marketing: "#marketing_label" }, // the label the person reads: its digest is attested (match / drift in the record)
// wording: { marketing: "#marketing_label" }, // or: write the published wording into the label
onCaptured: (r) => console.log("capture token", r.token, "wording", r.wordingVerdict),
onError: (e) => console.warn("capture failed; the form submitted anyway", e.message),
})
Nothing is recorded until your server commits the token with your application credential. The commit binds the person by the email you supply, freezes the receipt, sends the receipt email (unless you suppress it) and fires your webhooks.
# The capture token came in with the form submission; commit it with your application credential.
curl -X POST "https://overturo.com/api/v1/decisions/$CAPTURE_TOKEN/authorize" \
-H "Authorization: Bearer $OVERTURO_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "subject": { "email": "[email protected]", "external_id": "crm-42" } }'
# Your form handler receives the hidden field overturo_capture_token with the submission.
client = Overturo::Client.new(api_key: ENV["OVERTURO_API_KEY"])
decision = client.consent.decisions.authorize(
params[:overturo_capture_token],
subject: {email: params[:email], external_id: current_customer.id},
suppress_receipt: false
)
decision.status
decision.record_id # the consent record; its receipt is at /api/v1/consent_receipts/{record_id}
An uncommitted capture expires after 24 hours and leaves no record. Name the labels the person reads (labels, purpose โ selector) and the snippet attests a digest of each label's text with the capture, so the record says purpose by purpose whether your wording matched the wording published for the flow; or ask it to write the published wording into your labels (wording) so the two are the same by construction.
No server? Enable browser commit on the application (it requires allowed origins). The browser then commits in the same call and every such record carries a lower-assurance channel in its receipt.
2. Form platforms: TypeformBeta
A form hosted by Typeform cannot run the snippet and has no server to commit. Typeform's own signed submission webhook is the commit.
- On the application's Forms tab, connect the form: its form id and the flow it answers. Overturo gives you a webhook URL and a secret (shown once). With a Typeform personal access token connected, Overturo installs the webhook on the form for you and reads the form definition.
- Map the fields by reference: the email field; optionally a hidden field as your own identifier; one field per consent purpose (a yes/no or legal field, granted when true; or one choice of a multiple-choice, checkbox, dropdown or picture-choice field, granted when that choice is among the chosen); and one field per right-to-object purpose (the person must be able to object in the form, so it is mapped like a consent field).
- A consent purpose mapped to a field the definition marks required is refused at mapping time; so is a decision mapped to a field that cannot carry one (a rating, a number, free text), and a choice field mapped without the reference of the choice that means yes. Without a token the required flag cannot be read, so you attest that the field is optional and the receipt records that it was attested rather than read.
Each submission: the signature is verified (the previous secret is honoured for a day after a rotation), the response token is the idempotency key (a retry returns the existing record), the mapped field's title is the attested wording, the submitted time is the capture time, and a mapped consent field with no answer is a decline. Typeform's "send test request" is answered as accepted and records nothing; the connection shows "test delivery received".
Alternatives. A Typeform that redirects on completion can hand off to the hosted flow with the email recalled; the person confirms the address on the hosted page. A server that receives Typeform's webhook can call the Record API below. Prefer the connector when you want the wording drift verdict and no server; prefer the redirect when the person should see Overturo's own screen; prefer the Record API when you already process submissions server-side.
3. Your server: the Record API and the import
One call records a consent your server captured: the flow, the subject reference (email and optionally your identifier), the per-purpose decisions with their presentation, the time you captured it, an optional digest of the notice text, and an idempotency key of your choosing. A later request for the same person and flow with changed decisions produces a new record and withdraws the purposes that moved from granted to declined, so your webhooks fire.
curl -X POST https://overturo.com/api/v1/consent_records \
-H "Authorization: Bearer $OVERTURO_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"flow_id": "flw_...",
"subject": { "email": "[email protected]", "external_id": "crm-42" },
"decisions": { "marketing": { "decision": "granted", "default_checked": false, "mandatory": false } },
"captured_at": "2026-10-12T09:01:30Z",
"idempotency_key": "signup-42"
}'
client = Overturo::Client.new(api_key: ENV["OVERTURO_API_KEY"])
record = client.consent.consent_records.create(
flow_id: "flw_...",
subject: {email: "[email protected]", external_id: "crm-42"},
decisions: {marketing: {decision: "granted", default_checked: false, mandatory: false}},
captured_at: Time.now.utc.iso8601,
idempotency_key: "signup-42"
)
record.status
record.purposes
import { OverturoConsentRecords } from "@overturo/sdk"
const records = new OverturoConsentRecords({ baseUrl: "https://overturo.com", apiToken: process.env.OVERTURO_API_KEY })
const { consent_record } = await records.create({
flow_id: "flw_...",
subject: { email: "[email protected]", external_id: "crm-42" },
decisions: { marketing: { decision: "granted", default_checked: false, mandatory: false } },
captured_at: new Date().toISOString(),
idempotency_key: "signup-42",
})
console.log(consent_record.status, consent_record.purposes)
Historical consents are imported, not back-dated: each row keeps your original capture time as captured_at and the platform's time as recorded_at; the declared duration counts from your time; no receipt email goes out unless you ask; rows that break the rules above are refused by name. Run a dry run first.
# Dry run first: what would be recorded, what would be refused โ nothing is written.
curl -X POST https://overturo.com/api/v1/consent_records/imports \
-H "Authorization: Bearer $OVERTURO_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"flow_id": "flw_...",
"dry_run": true,
"records": [
{ "subject": { "email": "[email protected]" },
"decisions": { "marketing": { "decision": "granted" } },
"captured_at": "2025-03-01T10:00:00Z",
"idempotency_key": "crm-row-1" }
]
}'
# Then read the outcome: GET /api/v1/consent_records/imports/{id}
What flows back
- The person receives the receipt with the wording as published, your organisation named, and the account offer; a person with an existing account sees the record among their decisions with a "This was not me" action.
- Your
consent.granted,consent.revokedandconsent.purpose_revokedwebhooks carry the subject reference you supplied and the per-purpose outcome. - With a connected mailing audience, every record event is written to the member's marketing permissions and tags, and the member carries a merge field with the person's own records link; put that merge tag where your unsubscribe link is. The person can withdraw one purpose, object, or agree again from there.
- The connection's inbound endpoint receives the audience's unsubscribe and permission-change events. A change is applied only after the member is read back from the audience, and only as a withdrawal: a subscription made in the mailing tool is never recorded as a consent โ it is listed on the connection for you to resolve by capturing consent in your form. Changes made through the API are excluded from the subscription, so the two systems never echo each other.
- The status read answers for a subject reference as well as a person id:
# The per-purpose status for a person, by the reference you supplied (email or your identifier).
curl "https://overturo.com/api/v1/applications/$APPLICATION_ID/consent_status?subject[external_id]=crm-42" \
-H "Authorization: Bearer $OVERTURO_API_TOKEN"
The flow page's Records tab lists every record with its channel, platform, attester, wording verdict and subject reference. Manage your forms, Typeform connections and settings on the application's Forms tab; the receipt vocabulary is documented under Receipts.
Consent, notice & authorization receipts
Every completed decision issues a receipt aligned with ISO/IEC TS 27560:2023 โ a self-declared conformance to the Technical Specification, backed by the platform's receipts conformance suite. Receipts are independently verifiable: the signed export carries a detached signature anyone can check against published keys with no platform access.
One distinction, stated plainly. ISO/IEC TS 27560 is a personal-data-consent standard: conformance language applies to records that carry personal-data processing. A pure authority record follows the standard's structure and is 27560-aligned, never more.
The published schema
Schema version overturo-cr/2.13,
citing ISO/IEC TS 27560:2023. The
schema is published byte-verbatim at its own identifier:
https://overturo.co.za/schemas/receipt/v2/record.json โ the same URL every
machine-readable receipt carries as its $id-rooted
schema_version reference.
Its governed history, versioning policy, interpretation and verification guide are published by the Overturo Geneva Association, which governs the schema; the operating companies issue records under it, and so may anyone. Additive changes are minor versions; a breaking change is a new major at a new identifier, served beside this one for five years.
Discovery endpoints
| Endpoint | Purpose |
|---|---|
/schemas/receipt/v2/record.json |
The published receipt schema, byte-verbatim at its own identifier. |
/.well-known/witness-configuration |
External witness anchoring configuration and receipt signing-key discovery (current + historical region keys) |
/.well-known/transparency/:application_id |
Controller identification record for an application's receipts: declared parties, export flavors, and the schema, interpretation, and key-discovery pointers |
/.well-known/transparency/agents/:agent_id |
Agent accountability record: the operator's declared identity, the agent's platform-attested status, current credential attestations, and the receipt machine pointers |
/.well-known/transparency/agents/:agent_id/manifests/:version |
A published capability-manifest version: the agent's declared capability set frozen at pin time, with its content hash for independent verification |
The full well-known inventory lives on the API Reference tab.
Record kinds
consent_recordnotice_recordauthorization_record
Consent records carry the granted purposes and the consent lifecycle. Conformance language applies: the record carries personal-data processing.
Notice records cover notice-not-consent mechanisms and anonymous completions (no principal identifier โ the record identifier stands in). Conformance language applies to the notice it records; a notice event is never rendered as consent given.
Authorization records are durable evidence of a delegated-authority commitment. Every commitment freezes exactly one record at activation; lifecycle history is derived at render time and never rewrites the frozen record. A held runtime receipt resolves into the same record through the references both already carry (the dispute kit's join slice โ see the verification guide), and every agent-side fact is provenance-labeled: platform-attested registration facts versus operator-declared metadata, never blurred. Aligned only, unless the record carries a personal-data slice (a pii_processing section): pure authority semantics are outside the standard's subject matter.
Absence is meaningful throughout: a field the platform cannot state truthfully is omitted, never guessed โ each field's absence semantics are documented in the schema itself.
Every withdrawal and objection event names where it was made from: the person's own records, the link in their email, the link in their receipt, the organisation's mailing tool, a newer record from the organisation's form, the organisation or its systems, the end of the term, or the platform's subject sweep. A change made in the organisation's mailing tool is recorded as the person's withdrawal, never as a consent.
Overturo proves when the decision was recorded, which wording was in force, who attested it and that nothing has changed since; for a record an organisation captured in its own form, Overturo does not vouch that the control was shown unticked; for a record captured through the Overturo field, Overturo rendered the control, showed it unticked and recorded the person's own change, and does not vouch for what the page placed around it.
Sections & fields
The schema's own descriptions are the interpretation text:
record
The record's identity: the schema version it follows, the standard it conforms to, the record identifier, the kind, and whether the record is anonymous.
| Field | Required | Meaning |
|---|---|---|
schema_version |
yes | overturo-cr/2.13 |
conforms_to |
yes | ISO/IEC TS 27560:2023 |
record_id |
yes | The unique, stable identifier of this record โ the receipt's identity across every channel and export flavor, and the record-specific identifier that stands in for the principal on anonymous records. |
record_type |
no | The receipt kind. notice_record covers notice-not-consent mechanisms (informed, documented) and anonymous completions; its event section carries notice events, never a consent-given event. Absent on records issued before this field existed: read as consent_record. authorization_record covers delegated-authority commitments: it requires the authority section, carries pii_processing exactly when declared purposes exist, and its event section uses authority states. |
anonymous |
no | Kantara ANCR Stage 1 โ emitted (true only) when the subject completed without an established identity. The record-specific identifier standing in for the principal is record_id itself; pii_principal_id is FORBIDDEN below. |
pii_principal_id |
no | The principal reference. Required for identified records; forbidden for anonymous ones (the conditional below validates the rule โ anonymity is enforced, not conventioned). |
pii_processing
The granted purposes with their decision-time facts: language and jurisdiction, the notice as presented, the collection method, each purpose's lawful basis, categories, retention, storage and recipients, and how consent is withdrawn.
| Field | Required | Meaning |
|---|---|---|
language |
yes | The language the decision was presented in (locale code). Frozen labels throughout the record are in this language. |
jurisdiction |
yes | The jurisdiction governing this processing, as configured by the organization; "unspecified" when none is declared. |
privacy_notice |
no | The VERSIONED notice reference (ISO/IEC TS 27560 clause 6.3.4.2/clause 6.2.2.6): the notice as presented, content-addressed. The hashed content set is exactly {locale, presentation title+description, policy_url, flow-visible purpose rows (name/label/description/mechanism/required, sorted by name)} โ canonical deep-sorted JSON, sha256; growing the set is a schema minor, never silent. Three shapes coexist: locally-authored records always carry reference+hash (+url when the organization declared a policy URL); cross-region records carry what the authoring region's offer sent; records upgraded from the earlier flat shape carry url only (nothing is fabricated). Absent only for cross-region offers that predate this field and declared no policy URL. |
collection_method |
no | How the decision was collected, as shown to the subject (ISO/IEC TS 27560 clause 6.3.4.9) โ the human-readable form of extensions.collection_mechanism. |
purposes |
yes | The GRANTED purposes only, with their decision-time labels frozen (ISO/IEC TS 27560 clause 6.3.4.5). Purposes declined or later revoked do not appear here; revocations appear in the event lifecycle and the render-time status. |
storage_locations |
no | Where the data is stored (ISO country codes) โ derived from the platform's residency rules (where the subject's data lives), never declared by hand. |
processing_locations |
no | Present only when recorded lawful-transfer bases exist for the flow: their destination jurisdictions. Absent means no recorded cross-border processing. |
withdrawal_method |
no | The stable, credential-free entry point for withdrawing consent (27560 6.3.4.19). Never a minted access token: tokens expire, receipts do not. |
recipient_third_parties |
no | Party ids of third-party recipients (each has a corresponding party_identification row โ 27560 6.3.4.18). An EMPTY array asserts nothing is shared; an ABSENT key means the platform cannot enumerate recipients completely (e.g. externally-configured webhooks) and stays silent rather than implying completeness. |
privacy_rights |
no | Where the subject exercises their rights (27560 6.3.4.20): the stable access entry and, for full accounts, the self-service export. String encoding per the standard; labels live in the rendering layer. |
processing_method |
no | How the PII is processed (27560 6.3.4.11): the ENFORCED capability identifiers from the activation-frozen composition snapshot (signed with the flow). Identifiers only, never configuration. Absent for flows without a composition. |
impact_assessment |
no | Reference to the organization's completed legitimate-interest assessment (27560 6.3.4.22), emitted only when a granted purpose runs on legitimate interest and exactly one active assessment exists. |
party_identification
The controller (declared legal identity when the org completed its controller identity record โ provenance 'declared'; display-name fallback otherwise), the platform processor row, and the catalogued supervisory authority. Identity data is DECLARED by the organization, never verified by the platform. party_role values in use: controller, processor, supervisory_authority (string-typed โ later members may add roles).
| Field | Required | Meaning |
|---|---|---|
party_id |
yes | The party's stable public identifier โ for the controller, the application identifier the transparency record is keyed by; for recipients, the pointer recipient_third_parties references. |
party_name |
yes | The party's name: the declared legal name where an identity record exists (provenance "declared"), else the display name. |
party_role |
yes | The party's role in this processing: controller, processor, supervisory_authority, recipient - or, on authorization records, principal (a co-signing grantor) and agent (the authorized software agent). |
party_type |
no | Whether the party is an organization, a natural person, or a software_agent. Persons are never emitted as recipients. |
provenance |
no | One of: display_name, declared |
party_address |
no | โ |
party_email |
no | The party's declared contact email for privacy matters (ISO/IEC TS 27560 clause 6.3.6; the Kantara CR v1.1 required contact). |
party_phone |
no | The party's declared contact phone for privacy matters. |
party_url |
no | The party's declared contact or informational URL. |
authority
Authorization records only. States the enforced grant verbatim: each bound dimension appears exactly when the platform enforces it - absence means not configured, never 'unlimited'. Bound-dimension inner shapes are owned by the platform's authority-bounds vocabulary; this schema constrains presence, not structure.
| Field | Required | Meaning |
|---|---|---|
scope_bounds |
no | Which data scopes the agent may access or share (allowed/denied lists), as enforced. |
action_bounds |
no | Which operations the agent may perform (allowed/denied verb lists), as enforced. |
value_bounds |
no | Financial ceilings โ per transaction and cumulative, with currency โ as enforced. |
time_bounds |
no | The operating window (active days/hours) and hard expiry, as enforced. |
escalation_bounds |
no | Thresholds that force the agent to defer to its principal (approval triggers), as enforced. |
reputation_bounds |
no | Minimum counterparty trust requirements the agent must respect, as enforced. |
jurisdiction_bounds |
no | The jurisdictions the agent may act toward, as enforced at authorization time. |
sequence_bounds |
no | The eighth enforced dimension, carried on its own grant column; present only when configured. |
valid_from |
no | The declared start of the authority (ISO 8601). |
valid_until |
no | The declared end of the authority (ISO 8601); absent for open-ended grants. |
cooling_off |
no | ISO 8601 duration between commitment and activation; present only when an activation delay existed. |
withdrawal_method |
yes | Stable revocation entry URL; never embeds a credential - the principal authenticates on arrival. |
event
The lifecycle as a projection of the platform's existing evidence (tamper-evident audit-trail records, transaction timestamps) โ never a new event store. Consent records freeze the pre-decision prefix (notice shown, consent requested) plus the given event, in canonical lifecycle order; notice records freeze notice vocabulary instead โ see event_state. Renders append post-decision transitions (withdrawn โ optionally scoped by purpose_id โ and expired) derived at read time. Arrays are heterogeneous by construction: flows whose delivery path does not record an opening start at "consent requested"; records issued before the lifecycle section existed start at "consent given"; system-mediated records are notice records carrying the single constructive "notice shown". "consent refused" is defined but unissued in v1 (records exist only for completed decisions; refusal evidence belongs to the notice-receipt track). States "consent invalidated" and "consent halted" remain reserved: no platform transition claims them yet. Actor attribution: the principal for given/withdrawn/acknowledged (anonymous records substitute record_id); the controller (application) for notice shown/requested; the platform processor for expiry. Authorization records: the frozen prefix carries 'authority requested' and 'authority granted'; renders append the lifecycle derived from existing evidence (the grant's chronicle trail, the receipt-consumption aggregate) โ 'authority exercised' rows are AGGREGATES (see occurrences), suspension/resumption pairs are dated history, approvals carry the human decision with real attribution, and 'authority expired' is suppressed after a revocation. Records frozen before lifecycle events were recorded start at 'authority granted' (frozen means frozen). Histories without audit-trail records emit only what their columns can date.
| Field | Required | Meaning |
|---|---|---|
event_time |
yes | When the event occurred (ISO 8601). Times come from platform evidence โ never synthesized. |
validity_duration |
no | ISO 8601 duration (exact, second precision), present only when the consent carries an expiry. |
entity_id |
yes | Who acted: the principal reference for given/withdrawn/acknowledged (anonymous records substitute record_id), the application for notice shown/requested, the platform processor for expiry. |
event_type |
yes | One of: explicit, implicit, regular |
event_state |
yes | Notice records map evidence to notice vocabulary: interactive flows carry notice shown (from the display evidence) + notice acknowledged (completion, entity = principal or, for anonymous records, record_id); system/documented records carry a single constructive notice shown (entity = the application) and no acknowledged row. Notice records never carry consent given. |
purpose_id |
no | Present only on a scoped withdrawal: the withdrawn purpose's purpose_id (matches pii_processing.purposes[].purpose_id). Absent on record-level events. |
record_reference |
no | Re-affirmation linkage (Annex B re-confirm): on a given event, the record_id of the predecessor record this consent supersedes. The predecessor's render carries the inverse reference in receipt_metadata (render state, not frozen). |
occurrences |
no | Aggregate rows only (authority exercised): the authority was exercised this many times, most recently at event_time. Never present on single-occurrence events. |
approval_id |
no | Approval-scoped rows: the public reference of the human decision, matching the oversight evidence package's rows. Requested rows may lack it (the reference exists only once the decision row does). |
event_channel |
no | Where the change was made from (overturo-cr/2.13): the person's records, the management link in their email, the record link in their receipt, the organisation's mailing tool, a newer record from the organisation's form, the organisation, its systems, the end of the term, or the platform's subject sweep. Present only on appended withdrawal and objection events; absent on the frozen prefix. 'unspecified' names a change whose evidence predates the channel. |
extensions
Documented platform extensions (Kantara CR v1.1 clause 4.2; ISO/IEC TS 27560 clause 6.3.2.2): facts the standard has no slot for. Every key is documented here; nothing standard-named carries non-standard semantics.
| Field | Required | Meaning |
|---|---|---|
collection_mechanism |
no | The raw platform consent-mechanism value; pii_processing.collection_method carries the human label. |
delivery_mode |
no | How the decision surface was delivered: redirect, embed, popup, or link. |
scopes |
no | The effective granted platform permissions at fulfillment โ operational grant state, distinct from the purpose grants above. |
sensitive |
no | True when the flow collected at least one field the organization classified at a sensitive level. |
consent_flow_id |
no | The stable public identifier of the flow (the versioned notice reference is rooted in it). |
grant_integrity |
no | Authorization records only: the reference to the grant's dual signature - content hash + key versions. The signatures themselves are the signed-export payload, never inlined here. |
delegation_chain_root |
no | Authorization records only: the root commitment's record reference when this authority was sub-delegated. Absent for root grants. |
capability_manifest |
no | Authorization records only: the capability-manifest version the grant was made against โ the agent's declaration, content-addressed. The hashed content set is exactly {agent_type, name, delegated_scopes (sorted), and the operator-declared disclosure metadata: capabilities, operator_name, privacy_policy_url, data_storage, retention_period} โ canonical deep-sorted JSON, sha256; growing the set is a schema minor, never silent. agent_type/name/delegated_scopes are platform-attested registration facts; the metadata keys are operator-DECLARED (the hash anchors what was declared, not that it is true). A model/version fingerprint has no substrate field today and is documented-absent from the set. Manifest drift is AGENT-side (what the agent declares); composition drift is FLOW-side (what a flow executes) โ separate mechanisms, never conflated. Absent on records frozen before manifest pinning existed and when no unique agent resolves. Pinned at freeze; supersession never rewrites it (renders overlay manifest_superseded instead). |
accountability_record |
no | Structural slot: the agent accountability record reference in effect at activation. Documented-absent until that member ships. |
agent_disclosures |
no | Consent and notice records only: the AI agents disclosed to the human this record belongs to โ one entry per agent, EU AI Act Art. 50 aligned. A disclosure entry proves NOTICE (the human was told), never grant or authority: the commitment story lives in authorization records, and this array is schema-forbidden there. Present exactly when the flow disclosed agents (platform provenance) or the operator declared an out-of-band disclosure (declared provenance). Carries agent-side references only โ never the counterparty's identity (the anonymous kind stays anonymous through these fields). |
signature_evidence |
no | Signing-party verification evidence: one entry per completed evidence envelope anchored to the signing party, sorted by completed_at. Pointer + digest only โ verdict/claim CONTENT lives in the agreement export and evidence packages, never here. Present on consent records only, and only when the signing party's transaction carries completed evidence; honest-absent otherwise. |
collection_channel |
no | Where and by whom the decision was captured. hosted and embedded are Overturo-rendered; embedded_field and embedded_field_browser are Overturo-rendered controls placed inside the organisation's own form (committed by its server, or by the browser at lower assurance) and carry rendering_evidence; form_capture and form_capture_browser were captured in the organisation's own form by its own control (committed by its server, or by the browser at lower assurance); form_platform_webhook was captured in a hosted form builder and delivered by its signed webhook; controller_attested was posted by the organisation's server; imported was loaded from the organisation's historical records with its original capture time. For hosted, embedded, embedded_field and embedded_field_browser the control was rendered by Overturo. For every other channel Overturo attests when the decision was recorded, which wording was in force, who attested it and that nothing has changed since; it does not attest how the control was shown. |
collection_platform |
no | The hosted form platform that delivered a form_platform_webhook decision (for example typeform). Absent for every other channel. |
wording_drift |
no | Whether the wording the organisation attested its form displayed matched the wording published for the flow at the time of the decision. Recorded, never refused; absent when nothing was attested. |
captured_at |
no | When the organisation captured the decision in its own form, platform or records โ the decision event's time for a captured record. An import keeps the organisation's original capture time here. |
recorded_at |
no | When Overturo recorded the captured decision (the commit). For an import this is later than captured_at by design; the declared duration counts from captured_at. |
presentation_attested |
no | Purpose keys whose presentation (that the control was optional) was attested by the organisation rather than read from the form platform's definition. Absent when every presentation fact was read or observed. |
rendering_evidence |
no | Present only for a control Overturo rendered inside the organisation's form (collection_channel embedded_field or embedded_field_browser): who rendered it, the notice version shown, when, the host page's origin as asserted by the browser, whether the person changed the control with a trusted gesture, and the control's visibility when the form was submitted, with the kind of visibility measurement the browser offered. Absent for every other channel. |
The decision lifecycle
Event states (evidence-projected โ nothing is synthesized):
notice shownnotice acknowledgedconsent requestedconsent givenconsent refusedconsent withdrawnconsent expiredobjection recordedauthority requestedauthority grantedauthority exercisedauthority suspendedauthority resumedauthority revokedauthority expiredapproval requestedapproval grantedapproval deniedapproval countered
The frozen snapshot carries the pre-decision events; renders append post-decision transitions (withdrawal, expiry) derived from the same evidence at read time. The receipt's status is an accessor over this projection.
How a purpose counts as granted
Per purpose, by whichever shape recorded it: its opt-out result (accepted โ a legitimate-interest purpose the person did not object to, listed under that lawful basis), else its consent field (true), else โ recorded by neither shape โ completing the flow is the grant. Revoking a purpose later never rewrites the frozen record; the withdrawal appears in the lifecycle and the render-time status.
Notice versions
privacy_notice is a versioned reference to the notice as
presented: the fingerprint covers exactly the locale, the
presentation title and description, the policy URL, and the
flow-visible purpose rows (name, label, description, mechanism,
required). Same content, same version; any drift supersedes
automatically. The exact fingerprint construction is specified in
the verification guide.
Export flavors & verification
canonicalโ this schema's document (the default).dpvโ JSON-LD over the W3C Data Privacy Vocabulary (27560 Annex A.2). Authorization records that carry no personal-data processing refuse this flavor: pure authority semantics have no DPV vocabulary.signedโ the frozen record plus a detached signature, verifiable offline.
Request via ?flavor= on the receipt download,
GET /api/v1/consent_receipts/:id, or
GET /api/v1/authorization_receipts/:id. The
verification procedure โ canonicalization, signature encoding, key
discovery, the runtime-receipt join, and worked examples in two
languages โ is specified in the
verification guide,
published with the schema. The JSON-LD ovt: context IRI is
a namespace identifier and is not dereferenceable by design.
Prefer to see it rather than read it? Paste a signed record into the record debugger and watch it verify in your browser โ no signup, and the record never leaves the page.
Webhook delivery & retries
When an event fires, we POST a JSON body to each subscribed endpoint and read your response to decide what happens next. This page states exactly what our delivery does โ it is generated from the same delivery code that runs in production, so it is always current.
What counts as delivered
A delivery is acknowledged when your endpoint returns any 2xx status within 10s to connect and 15s to read the response. Return quickly and do the real work asynchronously โ a slow handler reads as a failed delivery.
Retries
A 4xx response is treated as a permanent
failure and is never retried โ the request was malformed or
rejected, and repeating it cannot help. A 5xx, a timeout, or
a network error is retried on this schedule:
5 attempts, with waits of:
- after 30s
- after 2m
- after 15m
- after 1h
After the final attempt the delivery is marked failed and an
entry is written to your audit trail. There is no manual retry queue: a
failed delivery stays failed, and you can inspect and replay individual
deliveries in the webhook debugger during development.
When a subscription is turned off automatically
To protect a broken endpoint from an endless backlog, a subscription is disabled automatically after 5 consecutive failed deliveries. A successful delivery resets the counter. Test deliveries never count toward this โ you can safely test against an endpoint that is temporarily down without disabling a live subscription.
Idempotency
Every delivery carries a stable event identifier in the
X-Overturo-Event-ID header. Retries of the
same event reuse the same identifier, so treat it as an idempotency key:
if you have already processed that identifier, acknowledge with a 2xx and
do nothing else.
Verifying the signature
Each request is signed with your subscription's signing secret so you can
confirm it really came from us. The
X-Overturo-Signature header carries the signature
and X-Overturo-Timestamp carries the timestamp
that was signed; the event type is echoed in
X-Overturo-Event-Type so your handler knows which
event it is. Verify the signature โ over the timestamp and the raw request
body โ before acting on any payload. The official SDKs include a one-call
helper, and the exact procedure is in the webhook signature-verification
guide.
Try it
Send a signed sample delivery to any of your saved endpoints โ with a live result, timing, and response excerpt โ from the test panel on the Webhooks page. A test fires the real, signed delivery pipeline, so what you see is exactly what a real event will do.
Ready to build?
Get an API token