Invisible trace watermarks
Invisible trace watermarks keep the visible composition unchanged while carrying a short authenticated locator in the image pixels. Use the locator to look up an asset, recipient, export job, or signed server record.
Embed and verify
Section titled “Embed and verify”import Marker, { ImageFormat } from 'react-native-image-marker';
const key = await loadWatermarkKeyFromTrustedStorage();const output = await Marker.embedInvisible({ image: { src: require('./photo.jpg') }, payload: 'asset-42', key, strength: 'robust', saveFormat: ImageFormat.png, filename: 'traced-photo',});
const result = await Marker.detectInvisible({ image: { src: { uri: output } }, key, strength: 'robust', search: 'robust',});
if (result.detected) { console.log(result.payload, result.confidence, result.bitErrorRate);}Native embedding returns a cache-file path; Web returns a data URL. Detection does not create a file.
When robust recovers a resized image, result.scale reports the successful
0.9, 0.95, 1.05, or 1.1 hypothesis. It is a detector estimate, not a precise
measurement.
Distribute recipient-specific copies in a batch
Section titled “Distribute recipient-specific copies in a batch”Each item can use a different locator, key, image, and output format. Results stay in input order; one failure does not reject the whole batch. Web runs at most four items concurrently, while native platforms stay serial to limit peak memory.
const controller = new AbortController();const outputs = await Marker.embedInvisibleMany( recipients.map((recipient) => ({ image: { src: source }, payload: recipient.locator, key, strength: 'robust', saveFormat: ImageFormat.png, })), { concurrency: 4, signal: controller.signal, onProgress: ({ settled, total }) => console.log(`${settled}/${total}`), });
const verified = await Marker.detectInvisibleMany( outputs.flatMap((output) => output.status === 'fulfilled' ? [{ image: { src: output.value }, key, search: 'robust' }] : [] ));Calling abort() stops new items from starting. Work already handed to Canvas
or a native decoder finishes normally and still reports its result.
Run Web detection in a Worker
Section titled “Run Web detection in a Worker”Robust detection is CPU intensive. Copy the standalone Worker artifact into a trusted same-origin static directory:
cp node_modules/react-native-image-marker/lib/worker/invisible-watermark.js \ public/worker/invisible-watermark.jsThen opt in for each detection:
const controller = new AbortController();
const result = await Marker.detectInvisible({ image: { src: suspectImage }, key, strength: 'robust', search: 'robust', worker: { scriptUrl: '/worker/invisible-watermark.js', signal: controller.signal, onProgress: ({ phase }) => console.log(phase), },});
controller.abort(); // terminates an active Worker and rejects with AbortErrorWithout worker, detection keeps the v1.11 cooperative main-thread behavior.
When a Worker is configured, script, protocol, and runtime failures are
reported instead of silently falling back. Never load this Worker from an
untrusted third-party origin: it receives both image pixels and the detection
key. The live Playground uses the same-origin artifact and lets
you compare both execution modes.
Embed and detect on a trusted server
Section titled “Embed and detect on a trusted server”Import the runtime through its explicit subpath. The normal React Native entry does not load this module, and the SDK does not install an image codec:
const { createInvisibleWatermarkRuntime,} = require('react-native-image-marker/trace-runtime.js');
const runtime = createInvisibleWatermarkRuntime({ codec: yourRgbaCodec, maxConcurrency: 4,});
const output = await runtime.embedInvisible({ image: { src: inputBuffer }, payload: 'asset-42', key: await secretManager.get('trace-watermark-key'), strength: 'robust', saveFormat: 'png',});The codec owns decoding, EXIF orientation, aspect-preserving maxSize, RGBA
conversion, and JPEG/PNG encoding. The runtime copies decoded pixels before
embedding and provides single and batch methods with the same result ordering,
progress, and cancellation semantics as Marker.
The independent Node.js Trace Service example
uses Node.js 22, sharp, a service-owned key, bounded JSON/base64 requests, an
injectable record store, and optional Content Credentials. Its in-memory store
and lack of authentication are local-demo defaults, not a production perimeter.
Payload and key rules
Section titled “Payload and key rules”payloadmust contain 1–12 UTF-8 bytes. Prefer a random locator such as a compact asset or distribution ID.keymust contain at least 16 UTF-8 bytes. Do not commit it, bundle a master key, or reuse the public Playground key.- Do not put names, email addresses, order details, or other personal data in the payload. Store that data behind the locator with appropriate access controls and retention.
- Detection authenticates the embedded frame with a truncated HMAC tag. It does not create a public digital signature.
Browser scripts and extensions can read any key available to page JavaScript. For production Web delivery, embed on a trusted server or issue narrowly scoped, short-lived keys to a controlled client.
Strength and search
Section titled “Strength and search”| Setting | Use it when | Trade-off |
|---|---|---|
subtle |
The cleanest pixels matter most | Least tolerant of later encoding |
balanced |
General photos and direct output | Default visual/robustness balance |
robust |
The image will be recompressed | Strongest pixel change |
search: 'fast' |
Original dimensions and block grid are preserved | Low detection cost |
search: 'robust' |
Limited crop or 0.9×–1.1× resizing is expected | More CPU and temporary memory |
Use the same strength for embedding and detection. On Web, detection yields
to the event loop while collecting blocks and searching candidates, so the page
can keep responding even though the total CPU work remains substantial.
What survives
Section titled “What survives”The project test matrix covers direct PNG output, JPEG re-encoding at quality 90/75/60, mild brightness and contrast changes, wrong-key rejection, limited crop recovery, and 0.9/0.95/1.05/1.1 resizing across six photographs in Chromium, Firefox, and WebKit. JPEG 75 re-encoding followed by light resizing is covered too. Real images and encoders vary, so validate your own delivery pipeline before relying on a threshold.
Heavy blur, resizing outside the tested range, large crops, painting over the
image, generative edits, screenshots, and print-camera workflows are not stable
guarantees. Detection returns detected: false instead of throwing when no
authenticated frame is found.
Add a signed Content Credential
Section titled “Add a signed Content Credential”The core package defines a small ContentCredentialsAdapter rather than
shipping a C2PA runtime or signing key. The composition order is fixed: embed
the locator first, then sign the resulting pixels.
const signed = await Marker.embedInvisibleWithCredentials({ watermark: { image: { src: source }, payload: 'asset-42', key, strength: 'robust', saveFormat: ImageFormat.png, }, claim: { title: 'asset-42.png', format: 'image/png' }, adapter,});
const credentials = await Marker.verifyContentCredentials({ image: signed.signedImage, adapter,});The independent Node.js C2PA service example
uses the official @contentauth/c2pa-node package and injects its certificate
and private key from the environment. dct-qim-v1 is not registered in the
C2PA Soft Binding Algorithm List, so the example uses a normal hard binding and
a private assertion containing only the algorithm and SHA-256(locator). It
does not claim c2pa.watermarked or standard soft-binding compatibility.
Recommended server workflow
Section titled “Recommended server workflow”- Generate a random locator for each exported copy.
- Store the locator, asset ID, recipient or channel, timestamp, and a signed audit record on the server.
- Embed only the locator after visible layers are composed and before final encoding.
- Keep the original and marked output long enough to investigate false negatives.
- When detecting, apply your own confidence policy and confirm the locator against the server record.
The frame format and threat model are specified in RFC 0003. Batching, resize recovery, responsive Web detection, and Content Credentials integration are specified in RFC 0004. The on-demand server runtime, Trace Service, Worker protocol, and reliability matrix are specified in RFC 0005.