Guide: embeds — Provenance ceremonies in your product
@provenance/embed drops our ceremonies (approve, verify, capture, sign and enroll)
straight into your UI as <provenance-*> web components. No redirects, no iframes.
Embeds give you one script, one auth model and one theming contract.
The widgets are approval, verify badge and receipt, capture, signing, enrollment, order review and trace. This page covers the foundation every widget shares.
The auth model (read this first)
Three moving parts, and the split is deliberate:
| Credential | Where it lives | What it can do |
| --- | --- | --- |
| Secret API key (prv_test_…) | your backend, never a browser | mint everything, including embed tokens |
| Publishable key (prv_pub_…) | safe to ship to the browser | identifies your account to the embed surface — and nothing else (zero data access) |
| Embed token (emb_…) | minted per-ceremony, short-lived (≤ 15m) | one widget, one object — the scoped bearer the widget uses |
Object-scoping is a server decision. Your backend calls POST /v1/embed/tokens with
your secret key to scope a token to exactly one object. A publishable key can never
reach that endpoint — so a value you ship to browsers can never mint a token for an
object you didn't authorize.
1. Mint a publishable key (once)
curl $PROVENANCE_API/v1/embed/publishable-keys \
-H "Authorization: Bearer $PRV_SECRET_KEY" -H "Content-Type: application/json" \
-d '{ "name": "marketing-site" }'
# → { "publishable_key": "prv_pub_…", "publishable_key_id": "01J…" }
2. Exchange your secret key for a scoped embed token (per ceremony, server-side)
curl $PROVENANCE_API/v1/embed/tokens \
-H "Authorization: Bearer $PRV_SECRET_KEY" -H "Content-Type: application/json" \
-d '{ "widget": "approve", "object_id": "appr_9f2…", "ttl": 600 }'
# → { "embed_token": "emb_…", "widget": "approve", "object_id": "appr_9f2…", "expires_at": "…" }
3. Render the component (browser)
<script type="module" src="https://unpkg.com/@provenance/embed"></script>
<provenance-approve
base-url="https://api.provenance.io"
publishable-key="prv_pub_…"
embed-token="emb_…"
theme="auto">
</provenance-approve>
<script>
document.querySelector("provenance-approve")
.addEventListener("pv:approved", (e) => console.log("approved", e.detail));
</script>
React (a thin, type-only peer — React is never bundled):
import { ProvenanceEmbed } from "@provenance/embed/react";
<ProvenanceEmbed baseUrl="…" publishableKey="prv_pub_…" embedToken="emb_…"
onEvent={(name, detail) => …} />
The rules we don't bend
- Ceremonies never degrade in an embed. WebAuthn/passkey runs top-level — components live in your page (not an iframe), so passkeys work; Shadow DOM is for style isolation only, never a security boundary. Passkey-absent → the same cross-device QR fallback our own pages use.
- Trust chrome is immutable. You can recolor the surface via
--pv-accent,--pv-bg,--pv-fg,--pv-muted,--pv-radius,--pv-font(ortheme), but the "Provenance" mark, the verdict/risk colors, and DRAFT-consent markers are a brand-integrity floor — no override can touch them. - Read/ceremony-only. Embed tokens never expose management actions (keys, policies,
revocation). The
/v1/embed/*surface returns only what the widget renders. - The server is the source of truth.
pv:*events (pv:ready,pv:approved,pv:denied,pv:verified, …) are your UI signal — never trust the event alone. Confirm the outcome server-side (webhook or a read of the object).
Theming
provenance-approve {
--pv-accent: #0c837a; /* your brand */
--pv-radius: 10px;
}
Anything outside the allowlist is ignored, and any value containing CSS control characters is dropped (the same sanitizer philosophy we apply to SMS).
Pricing
The platform (publishable keys and embed tokens) is free. Each widget carries its
own meter where the underlying action does (an approval still meters approval_resolved;
capture still meters the attest). Verification and enrollment are free, permanently.
Observability
embed_tokens_total{widget} and embed_requests_total{widget,status}, plus a
token-mint-abuse alert (a spike in minted tokens is the first sign of a leaked secret
key).
<provenance-approve> — the approval ceremony
Renders one approval request for your user to decide, in your product:
<provenance-approve base-url="…" publishable-key="prv_pub_…" embed-token="emb_…"></provenance-approve>
- The canonical, hash-bound action description is always shown — a
display_summarymay headline it, but never replaces it. Amount, agent name, expiry, and the integrator's grant context (when routed through the approver network) render alongside. - Approve runs WebAuthn/passkey top-level (the ceremony never degrades in an embed). A rule that demands biometric rejects a bare click server-side. Passkey-absent → a "continue on your phone" fallback, never a dead end.
- Consent mode (
mode="consent"on the embed token) relabels the chrome to Consent/Decline and records a consent receipt — the healthcare wedge, zero new machinery. The DRAFT consent marker is immutable. - Decided/expired approvals render a terminal state, never a form.
pv:approved/pv:deniedfire on completion — but confirm server-side (approval.resolvedwebhook).
<provenance-badge> + <provenance-receipt> — verify anywhere
The viral loop. Verification is client-side and FREE, permanently — no embed token for public artifacts (they're signed with public keys).
<!-- a chip: ✓ Verified · Provenance / ✗ Invalid / ⚠ Revoked / ⏳ Checking -->
<provenance-badge base-url="https://api.provenance.io" token="<intent-token-or-receipt>"></provenance-badge>
<!-- the human-readable receipt/attestation viewer with a live signature check -->
<provenance-receipt base-url="https://api.provenance.io" receipt="<receipt-jws>"></provenance-receipt>
- The badge verifies the ES256 signature offline against the issuer JWKS, then does the
keyless revocation check. An unreachable revocation check renders
"Verified (revocation unchecked)" — never a silent full-green. Click opens our
/verifypage. <provenance-receipt>renders who/what/when,biometric_bound, policy version, and the hashes (mono) with a live signature check. Public receipts render keyless; an account-held receipt renders via an embed token (scope: one receipt) — both paths are leak-tested. The "what this proves / doesn't prove" hint is immutable trust chrome.- These work cross-origin: the JWKS and the public revocation endpoint are CORS-open (keyless, read-only), so the badge verifies from any site.
- Both fire
pv:verifiedwhen the check finishes (they have no embed-token bootstrap, so nopv:ready). The event fires for every outcome — branch ondetail.state: the badge reportsverified | verified_unchecked | revoked | invalid, the receiptvalid | expired | invalid, each with thejti. Passsite-urlso the badge links to your Provenance site's/verifypage.
<provenance-enroll> — grow the approver network
The sleeper. Embed approver onboarding in your signup and every enrolling user joins the approver network — the "Sign in with Provenance" funnel.
<provenance-enroll base-url="…" publishable-key="prv_pub_…" embed-token="emb_…"></provenance-enroll>
- Mint the embed token scoped to a pending approver link (
POST /v1/approver-linksgives you thelink_id; scopewidget: "enroll", object_id: link_id). The widget is the embedded ACCEPT surface. - Flow: email (prefilled — it's your user) → passkey (top-level WebAuthn, cross-device
fallback) → optional phone + OTP → the link-grant consent (DRAFT-marked,
consent_versionrecorded, the grant scope shown exactly) → link active,pv:enrolled {link_id}. - The credential is created on the approver pool — never your account. Your page only ever sees ids and status; it never touches credential material or the approver's other links. All approver-network abuse controls apply, and the one-tap block renders in the widget. With the kill flag off, the widget shows an unavailable state.
- FREE — enrollment grows the network; you pay per routed approval, never per enrollment.
<provenance-capture> — attested capture in your portal
Capture a photo against a case, in the integrator's UI:
<provenance-capture base-url="…" publishable-key="prv_pub_…" embed-token="emb_…"></provenance-capture>
- Camera viewfinder → Capture photo → attest →
pv:attested {attestation_id, case_id}. The embed token is scoped to one case + capture permission. - Tier: browser capture is software tier (
native_capture: false) — a provenance record of the captured media tied to the case. Hardware-backed attestation (Secure Enclave / StrongBox) is the Provenance native SDK; the render says so. - Graceful degradation, verbatim: camera denied → a sample-image fallback flagged as a missing capture signal — never a silent dead end. GPS is optional.
- Media bytes flow only to the attest endpoint (privacy parity with our own page).
<provenance-sign> — embedded two-sided signing
One party's leg of a two-sided agreement, in your product — the DocuSign shape, powered by our execution-proof ceremony:
<provenance-sign base-url="…" publishable-key="prv_pub_…" embed-token="emb_…"></provenance-sign>
- Mint the embed token scoped to
"agreementId:partyId". It renders the content description + the content hash (mono) and a "verify your copy" hash-check — drop your local copy in and the widget hashes it client-side (the file never uploads) and shows a match/mismatch. - Sign runs WebAuthn over the exact signing pre-image
sha256(agreement ‖ content_hash ‖ party)— a swapped party or a byte-different copy can never complete the original agreement. Sequential mode shows "waiting for the other party". On completion the receipt link renders (pairs with<provenance-receipt>), andpv:signedfires. - The DRAFT-marked consent and the e-signature "not legally-binding by itself" disclaimer are immutable trust chrome.
<provenance-order-review> — clinical order review
A mode="clinical-order" configuration of <provenance-approve> for an EHR/telehealth app —
see the clinician-ordering guide for the full flow:
<provenance-order-review base-url="…" publishable-key="prv_pub_…" embed-token="emb_…"></provenance-order-review>
- Mint the embed token scoped to
widget: "order", object_id: orderId. It renders the structured order (drug/dose in mono), the agent's stated reasoning (labeled agent-supplied — we bind and attest it, we do not validate the medicine), then biometric authorize / decline. - The prescriber-tier badge (self-attested → NPI → license → DEA, or a red federally
excluded verdict) is immutable trust chrome — a verifier verdict, colored by the
non-overridable risk tokens, that no
--pv-*override can style away. - The ceremony never degrades: WebAuthn runs top-level and the server enforces the
exclusion / tier / controlled-substance co-sign gates regardless of the button.
pv:authorized/pv:declinedfire on completion — confirm server-side (theorder.authorizedwebhook). - Immutable disclaimer: not EPCS-certified; Provenance authorizes, it never places the order.
<provenance-trace> — show a model trace anywhere
A keyless view of a Trace share link. It is free, needs no publishable key or embed token, and works cross-origin. The share link is the credential.
<provenance-trace base-url="https://api.provenance.io" share-token="trs_…" theme="auto"></provenance-trace>
- It fetches the public masked render (
GET /v1/trace/share/:token) and verifies the trace manifest in the visitor's browser against the public JWKS. It then checks that the rendered graph hashes to the value the manifest binds:public_graph_hashfor a public link,graph_hashfor an account-mask link. - Only one state is green: "Trace manifest verified" (signature valid and graph matches). The
other states are:
- a graph that doesn't match its manifest, or an invalid signature, is red
- an unreachable JWKS is amber ("Could not check the signature")
- It shows the model, the backend (a sandbox replay says so), the canary readings, and the top neurons ranked by their measured |ablation Δ|. Masked neurons (canary members, private neurons) arrive opaque from the server, and the widget cannot unmask them.
- Immutable chrome: the verdict and reading colors, the Provenance mark, and the line "Proves what
computed this output — not that it is correct or safe." Pass
viewer-urlto add a "Full trace" link. pv:verifiedfires withdetail.state(verified | graph_mismatch | invalid | unverifiable) andtrace_id.pv:errorfires when the link is revoked, expired or unknown.- React:
import { ProvenanceTrace } from "@provenance/embed/react".