Limbo Integrity
Limbo Integrity attaches C2PA content credentials to media at publish time and validates them on the viewer's device.
Publishers sign video, image, audio, document, or live stream content. The viewer's browser validates the signature and surfaces the publisher identity bound to it. Validation occurs locally; asset bytes are not transmitted to a Limbo service.
Who this is for
Organizations that publish media at scale and need viewers to verify its provenance.
| Audience | Signing use case |
|---|---|
| Broadcasters | Sign live news feeds so affiliates and viewers can confirm the stream is unaltered. |
| News organizations | Bind editorial photos and videos to a verified organizational identity from camera to publication. |
| Stock libraries | Attach a tamper-evident credential to every licensed asset. |
| AI labs | Disclose synthetic origin on generated images and video. |
| Social platforms | Render a "signed by publisher" indicator on uploads from verified accounts. |
| Streaming services | Authenticate originals end to end so re-encoded copies remain verifiable. |
The two SDKs
Two SDKs, split along the trust boundary. Each capability lives in exactly one SDK.
- Issuer SDK
(
@limboai/verifox-sdk-issuer), in your Node.js process. Asset signing, live-stream signing, ingredients, thumbnails, identity, ISCC soft bindings, and watermarking. → Sign quickstart - Verifier SDK
(
@limboai/verifox-sdk-verifier), a WebAssembly module in the browser or Node.js. Asset verification, live-stream validation, trust anchors (with the official C2PA conformance trust list bundled and trusted by default), and recovery. → Verify quickstart
How it works
The boundary that matters is the line between your process and the Limbo signing service. Asset bytes never cross it.
The issuer SDK hashes the asset locally and sends only the hash to the signing service (C2PA 2.3 §15). The service custodies your organization's identity, signs the hash, and returns a C2PA signature with trusted timestamping. Signed bytes travel over your existing CDN. Your code never holds signing keys, and Limbo never holds your media.
- The signing service sits outside your media data-residency zone. Because it only receives hashes, it falls outside the scope of regulations that govern your media (GDPR, CCPA, sector-specific rules).
- Logs can only ever contain a hash. A signing-service log line, at worst, exposes a hash, never the underlying asset bytes.
The signing model
Sidecar output
AssetSdk.sign returns a C2PA sidecar: the manifest bytes, detached from the asset. You either embed it back into the container with embed (a self-describing signed file) or distribute it as a separate .c2pa file alongside the asset. Either way, the sidecar is the unit of provenance the verifier reads, whether it travels inside the bytes or beside them.
Where the manifest lives
sign's options.storage — a { mode } object — chooses where the manifest lives:
| Mode | Where the manifest lives | Use when |
|---|---|---|
{ mode: "embedded" } | Inside the asset bytes | The container can hold the manifest and you want a single self-contained file |
{ mode: "remote" } | On the Limbo registry; the asset carries a URL | The manifest is large (long ingredient chains) or the container can't hold it |
{ mode: "both" } | Embedded and on the registry | You want a self-contained file that's also recoverable if the manifest is stripped |
See Storage modes for how each behaves during delivery.
Two keys, two layers (streams)
Finished files are signed once. Live streams can't be, because invoking the organization's key once per segment is too slow. C2PA live streaming uses a two-tier model instead (C2PA 2.3 §18.25):
| Layer | What it signs | Where the key lives | How often |
|---|---|---|---|
| Long-lived org key + X.509 chain | The init manifest (which embeds the session key) | Inside the Limbo signing service | Once per stream init |
| Ephemeral session key | Each media segment | In your SigningVault | Every segment (sub-millisecond) |
The org key signs a short-lived session key once; that session key then signs each segment locally until it expires. The operational detail (vault and ledger contracts, rotation) lives in Stream storage.
The verification model
Validation is local
Once the asset bytes are fetched (over your CDN, from disk, or out of a video player's segment buffer) validation happens entirely on the device. The verifier SDK parses the embedded C2PA manifest, recomputes the content hash, and checks the signature against the caller's trust anchors plus, by default, the bundled official C2PA conformance trust list. Asset bytes are never sent to a Limbo service at verification time, and no network call is required to reach a verdict.
The single exception is registry recovery (below): that request carries a binding, a hash or ISCC, never the asset bytes.
Trust is the relying party's
Under the C2PA model, a manifest reaches the Trusted state only when its signing chain validates against an anchor list known to the verifier. Limbo puts that list in the relying party's hands rather than curating a canonical one: different publishers warrant different trust, and a central list would be a central failure surface. The SDK does bundle the industry's own C2PA conformance trust list and trusts it by default — but it is additive to your anchors, not Limbo's curation, and trustC2paAnchors: false switches it off entirely. The trade-off is that anchor rotation is the customer's responsibility. See Trust anchors for the full rationale and rotation patterns.
Embedded manifest vs registry recovery
Two ways a credential reaches the verifier:
| Embedded manifest | Registry recovery | |
|---|---|---|
| Where the manifest lives | Inside the asset bytes | In a registry, keyed by content binding (exact hash, or an ISCC/watermark soft binding) |
| Network at verify time | None | One lookup by binding (no bytes) |
| When it applies | Default, fastest path | No embedded manifest and a registry is configured |
| Configuration | Always available | registry option to AssetSdk.create |
Recovery is a fallback, not a replacement: verify tries the embedded manifest first and only consults the registry when none is present. It first looks up the exact content hash (a stripped or byte-identical copy), then tries each binder you pass, in order — supply Binder.iscc() and a re-encoded image still resolves via its recomputed ISCC image soft binding. See Recovery.
Assets vs live streams
| Asset | Live stream | |
|---|---|---|
| API | AssetSdk | StreamSdk |
| Lifecycle | Single signing operation | init, sign per segment, stop |
| State | None | Per-session |
Assets are finished files (photo, video, audio, PDF). The SDK hashes the file, requests a signature, and embeds the manifest back into the bytes.
Live streams are continuous (24/7 news, live sports). The SDK opens a session, registers a rotating set of short-lived keys, and signs each HLS or DASH segment as the encoder emits it. The viewer's player validates each segment on arrival.