Ir al contenido

Según su conexión, puede haber un sitio de Overturo más adecuado para usted: sus datos se almacenarían en Estados Unidos.

SDKs

Las bibliotecas cliente de Overturo: instalación, primeras llamadas, formatos de petición, errores — y los puntos de entrada de Formularios.

El cuerpo de la documentación está en inglés.

Choose your integration: Server-side REST API → Ruby SDK. Browser-side consent flow embed → JavaScript SDK.

JavaScript SDK

Browser-side SDK for embedding Overturo consent flows in your application's UI. For server-side REST API access (creating applications, managing flows, handling webhooks), see the Ruby SDK.

Don't need programmatic control? An HTML-only embed (<a> redirect or <iframe>) covers most integrations without npm or build tooling — see Embedding.

Installation

npm:
npm install @overturo/js
yarn:
yarn add @overturo/js
CDN:
<script type="module">
  import { Overturo } from "https://esm.sh/@overturo/js"
  // initialise as shown in "Initialization" below
</script>

Source: github.com/overturo/js

Initialization

Initialize the SDK with your publishable key. Use pk_test_* for sandbox and pk_live_* for production.

import { Overturo } from "@overturo/js"

const overturo = new Overturo({
  publishableKey: "pk_test_abc123"
})

The constructor also accepts:

  • apiKey — required for preview and token-exchange. Never expose in browser code shipped to end users.
  • baseUrl — API host. Defaults to the production endpoint.
  • flowBaseUrl — consent-flow host. Same value as baseUrl today; will diverge when the platform splits API and flow hosts.
  • timeout — request timeout in milliseconds.

Core concepts

A handful of distinctions that affect every call:

  • Publishable key vs. API key. The publishable key (pk_*) is safe in browser code — it can only mint consent sessions. The API key (sk_*) reads internal state (previews, token exchange, server-mints-the-session flows) and must never appear in client-side bundles.
  • Two host accessors today, one host in production. baseUrl (API host) and flowBaseUrl (consent-flow host) return the same value today — you can ignore the distinction. They'll diverge when the API and flow hosts split; using both accessors now means your code already handles that day.
  • Origin model. The SDK pins postMessage events to the flow origin and refuses to send to a blank target. The hosting page's origin must appear on your Application's Allowed Origins list or the browser blocks the SDK's API calls at preflight time.

Handle Consent Events

The embed communicates via postMessage events prefixed with overturo: (Trust Surface postMessage v2). Always verify the origin:

Listen for consent events:
window.addEventListener("message", (event) => {
  if (event.origin !== "https://overturo.com") return

  switch (event.data.type) {
    case "overturo:ready":
      console.log("Decision flow loaded; kind:", event.data.kind)
      break
    case "overturo:resize":
      iframe.style.height = event.data.height + "px"
      break
    case "overturo:state":
      // Visible state changed; payload is kind-specific.
      break
    case "overturo:complete":
      // Consent: outcome is "granted" or "denied".
      // Approval: outcome is "approve", "deny", or "counter".
      console.log("Decision outcome:", event.data.outcome)
      // Hand event.data.exchange_token to your backend; it's the source of truth.
      break
    case "overturo:cancel":
      console.log("User cancelled:", event.data.reason)
      break
    case "overturo:error":
      console.error("Flow error:", event.data.code, event.data.message)
      break
    case "overturo:deadline":
      // Server-pushed deadline tick (T-30s, T=0); update countdown UI.
      console.log("Remaining:", event.data.remaining_seconds, "seconds")
      break
  }
})
Event Description Payload
overturo:readyIframe loaded and readykind, deadline, session_token, branding
overturo:resizeContent height changedheight
overturo:stateVisible state changed (kind-specific payload)kind-specific
overturo:completeUser decided (granted/denied for consent; approve/deny/counter for approval)outcome, exchange_token
overturo:cancelUser declined or dismissedreason
overturo:errorAn irrecoverable errorcode, message
overturo:deadlineServer-pushed deadline tick (T−30s and T=0)remaining_seconds

Complete Example

Full integration with session creation, iframe, event handling, and auto-resizing:

Full consent embed integration:
async function embedDecisionFlow(containerId, flowId) {
  // 1. Mint a decision token from your backend.
  const res = await fetch("/api/decision-session", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ kind: "consent", flow_id: flowId })
  })
  const { decision_url, session_token } = await res.json()

  // 2. Drop the custom element (or create an iframe pointed at decision_url).
  const iframe = document.createElement("iframe")
  iframe.src = decision_url
  iframe.style.cssText = "width:100%; border:none; min-height:400px;"
  document.getElementById(containerId).appendChild(iframe)

  // 3. Listen for postMessage v2 events.
  window.addEventListener("message", (event) => {
    if (event.origin !== "https://overturo.com") return
    switch (event.data.type) {
      case "overturo:resize":
        iframe.style.height = event.data.height + "px"
        break
      case "overturo:complete":
        iframe.remove()
        // Hand exchange_token to your backend; verify via /api/v1/decisions/:token/exchange.
        onDecisionComplete(event.data.outcome, event.data.exchange_token)
        break
      case "overturo:cancel":
        iframe.remove()
        onDecisionCancelled(event.data.reason)
        break
    }
  })
}

await embedDecisionFlow("decision-container", "flw_abc123")

Error Handling

Handle API errors:
import { Overturo, OverturoApiError } from "@overturo/js"

const overturo = new Overturo({ publishableKey: "pk_test_abc123" })

await overturo.createSession({ flowId: "flw_doesnt_exist" })
Status Meaning Action
401UnauthorizedVerify publishable key
422Validation FailedCheck error.details
429Rate LimitedExponential backoff

Security: Publishable keys are safe for client-side code. They can only create consent sessions — never expose API tokens in the browser.

Preview tokens

Preview an unpublished template or in-progress setup session inside your own UI before publishing it. Returns a handle that controls device/theme/visibility.

Internal tooling only. previewTemplate() needs the secret API key in the same execution context that mounts the iframe. Use it in staging dashboards or admin UIs you trust — never in code shipped to end users.

Render a template preview:
import { Overturo } from "@overturo/js"

// ⚠ INTERNAL TOOLING ONLY. previewTemplate() reads internal state and
// requires the secret API key in the same execution context that mounts
// the iframe. Run this in a staging dashboard or admin UI you trust —
// never in code shipped to end users.
const overturo = new Overturo({
  publishableKey: "pk_test_abc123",
  apiKey: process.env.OVERTURO_API_KEY,
})

const preview = await overturo.previewTemplate({
  templateId: "tpl_abc123",
  container: "#preview",
})

// preview.setDevice("desktop" | "mobile")
// preview.setTheme("light" | "dark")
// preview.destroy()

Token exchange

The consentToken you receive on completion is a single-use receipt — not the data. Exchange it server-side (with your API key) for the resolved claims, third-party attestations, and any verifiable credentials issued by the flow.

Exchange a consent token for claims:
import { Overturo } from "@overturo/js"

// Token exchange must run server-side — it requires the secret API key.
// (Use `pk_*` alone on the client; reach for `sk_*` only in trusted contexts.)
const overturo = new Overturo({
  publishableKey: "pk_test_abc123",
  apiKey: process.env.OVERTURO_API_KEY,
})

// Server-side flow:
//   1. createSession()  → hand the returned sessionToken to the browser
//   2. browser embeds the flow and receives a consentToken on completion
//   3. POST the consentToken back to your server, then exchange() it here
const result = await overturo.exchange({
  sessionToken: "ct_session_token_from_step_1",
  consentToken: "ct_consent_token_from_step_2",
})

result.claims
result.attestations
result.credentials

When not to use this SDK

If you only need to authenticate users — no consent UI, no claims exchange — you don't need this SDK at all. Your Application is a standards OAuth 2.0 / OIDC client. Use any compliant library: openid-client (Node), Authlib (Python), oidc-client-ts (browser SPA).

Pre-filled OIDC snippets for your Application live on Connect → your Application → Integration → Authentication only. The auth-vs-consent gate on the Embedding docs page also walks through the decision.

Consent in your own form

Two entry points: overturo.forms.attach captures your own control at submit (the capture snippet); the field entry renders the Overturo field inside your form. Both hand your server a capture token to commit. Full guide: Forms.

The capture snippet:
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),
})
The Overturo field:
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.

Also in the package, not on this page: createSetupEmbed, createSetupPopup, and setupRedirect embed the flow setup wizard. They remain callable for internal partners, but the setup-wizard surface is not yet generally available — a follow-up spec will promote them once it ships.

Ruby SDK

Server-side gem for managing Overturo from your backend (Rails, Sinatra, or any Ruby app). For browser-side consent flow embedding in your application's UI, see the JavaScript SDK.

Installation

Gemfile:
gem "overturo"
Then:
bundle install

Source: github.com/overturo/sdk-ruby

Configuration

Create a client with an API token from Credentials.

Initialize once, share across requests:
require "overturo"

client = Overturo::Client.new(api_key: ENV.fetch("OVERTURO_API_KEY"))

Security: Store API tokens in environment variables or Rails credentials, never in source code.

Get Your Application Credentials

Applications are created in the dashboard. After signing up, create an application and copy its id and publishable_key — they're the first argument to every nested-resource call below.

Application creation is a dashboard-only flow
# Applications are created in the Overturo dashboard, not via the API.
# Sign up at https://overturo.com, create an application, then copy the
# id (e.g. "app_abc123") and publishable key (e.g. "pk_live_...") from
# the application's settings page. Generate an API key from the API
# tokens page and set it in your environment as OVERTURO_API_KEY.
#
# The SDK does not expose POST /api/v1/applications — application
# creation is a dashboard-only flow. The SDK manages resources nested
# under an existing application: flows, webhooks, connections, etc.
# The application_id from your dashboard is the first argument to every
# nested-resource call (see the next snippet).

application_id = "app_abc123" # from your dashboard
publishable_key = "pk_live_demo123" # from your dashboard
puts "Configured for application: #{application_id} (#{publishable_key})"

Create a Consent Flow

Flows are nested under the application that owns them. Replace app_abc123 with the id from the previous step.

POST /api/v1/applications/:application_id/flows
flow = client.connect.flows.create(
  "app_abc123", # application_id (positional — from your dashboard)
  name: "Privacy Policy",
  pattern: "standard",
  purposes: ["data_sharing", "analytics"]
)

flow.id
flow.status

Start a Consent Session (server-side)

Mint the consent session server-side so your API key never reaches the browser. Hand the resulting session_token to the client, then call overturo.embed({ sessionToken }) from the JavaScript SDK's low-level consent section.

POST /api/v1/consent_sessions
session = client.consent.consent_sessions.create(
  flow_id: "flw_xyz789",
  delivery_mode: "embed",
  embed_origin: "https://myapp.com"
)

session.session_token
session.embed_url

Verify a Consent Token

After the user completes the flow, verify the consent token your frontend received before trusting it.

GET /api/v1/consent_sessions/:session_token/verify
result = client.consent.consent_sessions.verify(
  "ct_abc123",
  consent_token: "ct_abc123"
)

result.valid
result.status

Handle Webhooks

Webhook deliveries carry three identifying headers and an HMAC-SHA256 signature. The signed message is "<timestamp>.<raw body>" — include both when reconstructing the signature.

Header Description
X-Overturo-SignatureHMAC-SHA256, format v1=<hex>
X-Overturo-TimestampUnix epoch seconds; reject if older than 5 min
X-Overturo-Event-IDUnique per delivery; use for idempotency
X-Overturo-Event-Typee.g. consent.granted
Webhook handler with signature + replay protection:
class WebhooksController < ActionController::API
  TOLERANCE_SECONDS = 5 * 60

  def receive
    body      = request.body.read
    signature = request.headers["X-Overturo-Signature"].to_s
    timestamp = request.headers["X-Overturo-Timestamp"].to_i

    return head :unauthorized if (Time.now.to_i - timestamp).abs > TOLERANCE_SECONDS

    expected = "v1=" + OpenSSL::HMAC.hexdigest(
      "SHA256", ENV.fetch("OVERTURO_WEBHOOK_SECRET"), "#{timestamp}.#{body}"
    )

    unless signature.bytesize == expected.bytesize &&
           ActiveSupport::SecurityUtils.secure_compare(signature, expected)
      return head :unauthorized
    end

    event = JSON.parse(body)
    case event["event"]
    when "consent.granted"  then handle_consent_granted(event)
    when "consent.revoked"  then handle_consent_revoked(event)
    when "flow.activated"   then handle_flow_activated(event)
    end
    head :ok
  end

  private

  def handle_consent_granted(_event); end
  def handle_consent_revoked(_event); end
  def handle_flow_activated(_event); end
end

Manage webhook subscriptions programmatically via client.connect.webhooks.create(application_id, url:, events:).

Verify the Audit Chain

Every audit event is hash-chained and Merkle-anchored every 15 minutes. Pull anchors to prove the integrity of any audit record:

GET /api/v1/anchors
anchor = client.comply.anchors.retrieve("anc_1")
anchor.merkle_root

receipts = client.comply.anchors.receipts(anchor.id)
receipts.first.witness

Error Handling

The SDK raises a typed subclass of Overturo::Error for every non-2xx response — rescue the specific class you care about, or the base class as a catch-all.

A 422 response from the API surfaces as Overturo::InvalidRequestError:
caught = begin
  client.connect.flows.create("app_abc123", name: "")
rescue Overturo::InvalidRequestError => e
  e
end

caught.http_status
HTTP Error Responses
Status SDK Class Action
400InvalidRequestErrorCheck request body
401AuthenticationErrorVerify API token
403ForbiddenErrorInsufficient permissions
404NotFoundErrorResource doesn't exist
422InvalidRequestErrorCheck validation errors in e.json_body
429RateLimitErrorRespect Retry-After header
5xxApiErrorRetry with exponential backoff

Capture consent in your own form

You keep your sign-up, lead, intake, booking or checkout form. Overturo keeps the record: the versioned wording, the signed receipt, the withdrawal path and the audit evidence.

The full guide, with the rules a captured consent must follow, is in the documentation: Forms documentation

Your own form

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, shows the control unticked and observes the person's change in a frame your page cannot script; at submit the field hands your form the capture token and your server commits it as for the snippet. The receipt says the control was rendered by Overturo.

In your page:
<!-- 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>
Attach it to the 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.

Appearance is a bounded vocabulary applied inside the frame (fontFamily: system, inter, serif, mono; fontSize; text; muted; accent; border; radius; density: compact, regular, relaxed), plus theme and locale attributes. The field renders no required control: your terms checkbox stays your own. One field per flow per form; the application needs at least one allowed origin.

The snippet: your own control, captured at submit

Add the SDK, map each consent checkbox to its purpose and name the email field. At submit, the snippet captures the decision and hands your server a capture token; your server commits it with the application key.

In your page:
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),
})
On your server, when the submission arrives:
# Your form handler: the capture token arrived in 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}
)
decision.status
decision.record_id  # the consent record (its receipt lives at /api/v1/consent_receipts/{record_id})
decision.channel

Typeform

Connect a Typeform form once: name the form and the flow, map its fields to the flow's purposes, and let Typeform's signed submission webhook commit each consent.

Alternatives: a Typeform that redirects on completion can hand off to the hosted flow with the email recalled, and a server that receives Typeform's webhook can call the Record API. The documentation says when to prefer each.

Mailchimp: the withdrawal that travels

Connect an audience once and map a flow's purposes to its marketing permissions and tags. Every opt-in and every withdrawal — on the records page, by expiry, by "this was not me" — is written to the audience, and a daily check reads it back. The audience's own unsubscribes and permission changes come back the other way (read back before anything is withdrawn), and every member carries a merge field with the person's records link for your footer.

Server and import

Record a consent your server captured in one call, or import the consents you already hold with their original capture time. A dry run reports what would be recorded and refused.

Record one consent:
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"
  }'
Ruby:
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
Node:
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)
Import historical consents:
# 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}

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.

¿Listo para empezar?

Obtener un token de API