Skip to main content

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.

AudienceSigning use case
BroadcastersSign live news feeds so affiliates and viewers can confirm the stream is unaltered.
News organizationsBind editorial photos and videos to a verified organizational identity from camera to publication.
Stock librariesAttach a tamper-evident credential to every licensed asset.
AI labsDisclose synthetic origin on generated images and video.
Social platformsRender a "signed by publisher" indicator on uploads from verified accounts.
Streaming servicesAuthenticate 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 npm (@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 npm (@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.

Two consequences
  • 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:

ModeWhere the manifest livesUse when
{ mode: "embedded" }Inside the asset bytesThe container can hold the manifest and you want a single self-contained file
{ mode: "remote" }On the Limbo registry; the asset carries a URLThe manifest is large (long ingredient chains) or the container can't hold it
{ mode: "both" }Embedded and on the registryYou 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):

LayerWhat it signsWhere the key livesHow often
Long-lived org key + X.509 chainThe init manifest (which embeds the session key)Inside the Limbo signing serviceOnce per stream init
Ephemeral session keyEach media segmentIn your SigningVaultEvery 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 manifestRegistry recovery
Where the manifest livesInside the asset bytesIn a registry, keyed by content binding (exact hash, or an ISCC/watermark soft binding)
Network at verify timeNoneOne lookup by binding (no bytes)
When it appliesDefault, fastest pathNo embedded manifest and a registry is configured
ConfigurationAlways availableregistry 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

AssetLive stream
APIAssetSdkStreamSdk
LifecycleSingle signing operationinit, sign per segment, stop
StateNonePer-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.