Skip to main content

Watermarking

Watermarking adds an invisible, pixel-domain watermark so even a copy with its C2PA manifest stripped can be matched back to its credential. It is the image-only soft binding: the signal lives in the pixels, so it survives re-encoding, screenshotting, and metadata loss.

AssetSdk.watermark takes an already-signed image, watermarks it, re-embeds a fresh C2PA manifest over the watermarked bytes (linking the original as its parent), registers it for recovery, and returns the watermarked and re-signed bytes plus the registry id the re-signed manifest was stored under. It works in both the Node and browser builds.

Images only

Watermarking covers image assets (JPEG, PNG, WebP, and similar). For video masters, durability comes from the ISCC soft binding instead.

Sign, then watermark

Watermark a signed asset: sign and embed first, then pass those bytes to watermark. Both operations run against the one deployment you connected to.

watermark-photo.ts
import { SignerClient, AssetSdk, embed } from "@limboai/verifox-sdk-issuer";
import { readFile, writeFile } from "node:fs/promises";

const client = await SignerClient.connect("https://api.trylimbo.com", {
headers: { authorization: `Bearer ${process.env.LIMBO_API_KEY}` },
});
const sdk = AssetSdk.create(client);

const original = await readFile("photo.jpg");

// 1. Sign + embed as usual.
const { sidecar } = await sdk.sign(original, "photo.jpg", {
origin: { sourceType: "digitalCapture" },
}, { storage: { mode: "embedded" } });
const signed = embed(original, sidecar);

// 2. Watermark the signed bytes. The MIME is inferred from the bytes;
// pass `format` in the options to override it.
const result = await sdk.watermark(signed, { fileName: "photo.jpg" });

await writeFile("photo.watermarked.jpg", result.bytes);

result.bytes is the final image: watermarked and re-signed, ready to publish.

What you get back

interface WatermarkOutcome {
bytes: Uint8Array; // watermarked + re-signed image
manifestId: string; // registry id the re-signed manifest was stored under
fileName: string;
mimeType: string;
}

Options

interface WatermarkOptions {
fileName?: string; // recorded with the recovery entry
skipSignpost?: boolean; // skip the optional second watermark layer
format?: string; // asset MIME; inferred from the bytes when omitted
}

Recovery

When a stripped copy turns up with no C2PA metadata of its own, the verifier recovers the manifest from the watermark in the pixels. Watermark detection is a binder on the verifier SDK: Binder.watermark({ endpoint, token }), where endpoint is your deployment's full detect URL and token is your API key, sent as a bearer credential. Pass the binder to verify and it detects the watermark and recovers the manifest in one step:

import { AssetSdk, Binder, DEV_CA } from "@limboai/verifox-sdk-verifier";

const verifier = AssetSdk.create({ trustAnchors: [DEV_CA()], registry: "https://api.trylimbo.com" });

const results = await verifier.verify(stripped, [
Binder.watermark({
endpoint: "https://api.trylimbo.com/api/v1/watermarks/detect",
token: process.env.LIMBO_API_KEY,
}),
]);

Or derive the binding on its own — the "detect" primitive — to inspect or cache it before verifying:

const binding = await Binder.watermark({ endpoint, token }).detect(stripped); // { alg, value } | undefined

It returns the soft binding the copy recovers by, or undefined when no watermark is found. The credentials live on the binder you construct — the verifier itself stays credential-free.

The watermark binder is for the watermark specifically. For an ordinary re-encode with no watermark, you don't need it: pass Binder.iscc() and the verifier recomputes the image's ISCC Image-Code and recovers by it after the exact-hash lookup misses.

See Recovery.

note

Watermarking is a REST call to your deployment's watermarking service — a different endpoint than the signing service, reached through the same deployment and credentials. The service must be enabled there; if it isn't, watermark throws.

See also

  • Soft binding: the video soft binding, compared against watermarking
  • Sign an asset: the signing step that precedes watermarking
  • Recovery: verifier-side soft-binding lookup