Pin fragment spend to your account with signed tokens
Reading fragments is public and free of credentials. Writing new fragment copy is called minting; it spends your credits, and it is the request worth protecting.
Why the Origin header is not enough on its own
Section titled “Why the Origin header is not enough on its own”Without a token, a write request is admitted on its Origin header plus the
origin registry for your organization. A browser sets Origin honestly and
cannot be talked out of it, so this does stop drive-by mints from other
people’s web pages.
It stops nothing else. Any server-to-server caller sets Origin to whatever
string it likes, so the header proves “a browser sent this”, not “your
organization sent this”. A browser cannot hold a secret either, since anything
you ship in a bundle is public, so the proof cannot be produced in the page.
A signed token moves the proof to the one place that can hold a secret: your backend.
How the token flow works
Section titled “How the token flow works”- Your backend calls SparkleTree with your API key and describes the fragment call site.
- SparkleTree returns a token bound to your organization and that exact context and field schema, valid for 300 seconds.
- Your page hands the token to the provider. The browser only echoes it.
A forger without your API key cannot produce a token, and a leaked token buys one context for a few minutes rather than your budget.
Issue a token from your backend
Section titled “Issue a token from your backend”curl -X POST "$SPARKLETREE_API_BASE/api/embed/v1/fragments/mint-token" \ -H "x-api-key: $SPARKLETREE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "context": { "page": "pricing", "intent": "signup" }, "schema": [ { "key": "cta", "kind": "cta", "fallback": "Start your free trial" } ] }'{ "token": "stmt_v1.eyJ…", "expiresAt": "2026-08-09T12:05:00.000Z", "ttlSeconds": 300, "contextHash": "9f2c…", "schemaHash": "41ab…"}This endpoint requires a real credential, an organization-scoped st_ API key
or a signed-in session, and is the only part of the fragment flow that does.
Never call it from a browser.
The organization comes from the key, never from the body. Both hashes are recomputed on the server, so send the same context and fields your call site declares. A mismatch is a refused write, not a quiet downgrade to the weaker check.
Hand the token to the page
Section titled “Hand the token to the page”// Define this outside render, or wrap it in useCallback: a new function// identity on every render re-renders the provider's consumers.async function mintToken() { const res = await fetch("/api/sparkletree/mint-token"); // your own endpoint return (await res.json()).token;}
<SparkletreeProvider publishableKey={KEY} mintToken={mintToken}> {children}</SparkletreeProvider>A plain string works too, and useFragments({ mintToken }) overrides the
provider for one call site.
Prefer the function. It is called at the moment a write is attempted, so a tab left open past the 300-second expiry fetches a live token instead of echoing a dead one.
The token travels on the write request only. The read request is a shared cacheable object and stays identical for every caller.
Workspaces created from 2026-08-09 require this
Section titled “Workspaces created from 2026-08-09 require this”Signed tokens are the default for new workspaces. Wire the endpoint above before your first fragment call site. Until you do, writes are refused and every fragment serves your fallback copy. Pages render normally; nothing breaks, nothing adapts.
Workspaces created earlier keep the origin-verified path and can turn signing on whenever they have a backend for it. Either way the switch is Settings → Fragments → Signed mints only, and turning it off is a deliberate, recorded admin decision.
Which requests are admitted
Section titled “Which requests are admitted”Before any of this, the browser decides whether to send the write request at
all. If your apiBase came from a publishableKey and is not a SparkleTree
host or loopback, the SDK withholds the mint: a publishable key is an address
anyone can hand-encode, and a doctored one pointing at another host would
receive your signed token and replay it for its lifetime. Self-hosting is
what the explicit apiBase prop is for. Pass apiBase and
organizationId to the provider and the mint goes out normally.
Left unfixed, this is the one failure that never resolves itself: reads keep answering, your fallbacks keep rendering, and no copy is ever written. A dev build says so once in the console.
Then, server-side, in order:
- A valid token is admitted, whatever the
Originsays. This is the only tier that survives a spoofed header. - A token that is present but invalid, expired, or bound to different context is refused outright, never downgraded to the origin check, which would make the strong path decorative.
- With no token, an organization set to require signing refuses.
- Otherwise, a
localhostorigin is admitted outside production, and an origin verified for your organization is admitted and bounded by your daily budget. - A request with no
Originat all is refused.
If your token endpoint is down
Section titled “If your token endpoint is down”The write request goes out unsigned and the server decides. A render never waits on your auth code and never fails because of it. If your organization requires signing, the write is refused and the visitor reads your fallback copy, which is what they were reading anyway.