Whisper · Docs
Content

Platform integrations

Whisper sits beside the C2PA / Content Credentials stack you already run and anchors exactly one thing: a publicly verifiable signer identity at the DNS/DANE boundary. That's the layer where, today, an off-list signer and a globally trusted newsroom produce a manifest with the same technical appearance, and the verifier has only a private, gate-kept trust list to tell them apart.

Everything below is a proposed integration pattern (our design, not a vendor endorsement), and every one is written the same way: Whisper complements the mechanism you have; it never replaces it, and it never forks C2PA. We do not create the C2PA manifest, we do not embed the durable watermark, we do not assert that signed content is true, and we do not stop a screenshot from stripping the credential. Those are the manifest's, the watermark's, and the first-mile's jobs, and they are each already someone's. Whisper's job is the one seam C2PA depends on but does not itself provide: making the signer a manifest already references publicly and independently verifiable, expressed as an IPv6 /128 and a DANE-EE pin that DNSSEC and RDAP can prove and one call can revoke.

One spine under all of them. Each integration below reuses the same shipped primitive: a deterministic /128 derived from a signer's public key plus the C2PA signer-certificate serial it already carries in every manifest, passed as device_id (Signer & C2PA identity), published with a DANE-EE 3 1 1 pin and an RDAP record, verifiable by anyone with no account. The integrations differ only in where in the provenance chain that signer identity is bound and checked.

Anchor the trust decision, not the manifest

A Content Credential carries several trust mechanisms, each correct for its own layer, and the incumbents do each of them well. The mistake is to imagine a public signer anchor has to displace one of them. It doesn't. Whisper is deliberately confined to the one place none of them answer publicly: the moment a validator holds a valid signature and has to decide whether to trust the signer behind it: a decision that today resolves through a curated CA allow-list, an off-list "unknown source" penalty, or nothing at all.

Layer Owner / mechanism Whisper's role
Hard binding (the asset bytes) C2PA manifest: c2pa.hash.data / .bmff, JUMBF-embedded none (never creates the manifest or the hash)
Claim signature COSE_Sign1 + in-band x5chain (RFC 9360) none (the signature format stays exactly as C2PA defines it)
Durable soft binding Watermark / fingerprint (survives a screenshot / re-encode) none (anchors identity, not pixels; pair with it)
Signer trust decision (DNS/DANE) Curated C2PA Trust List / off-list "unknown source" / self-signed anchors here (the signer's own DNSSEC zone vouches for the cert serial, verifiable with no list to join)

Read the last row as the whole thesis. C2PA is explicit that a verifier trusts a signer if its certificate is on an allowed list or chains to a root on a trust-anchors list, and it does not mandate any particular list or PKI: trust lists and anchors are pluggable configuration inputs to the validator. That is the honest opening. A DANE/DNSSEC anchor is therefore a legitimate additional trust source, not a fork of the standard. Identity today is a claim only a gate-kept list can adjudicate; Whisper turns it into a network fact: the signer's cert serial, pinned in DANE, published in the signer's own DNSSEC-signed zone, verifiable by anyone against the IANA root.

owned by the manifest / the signer's CA / watermarking: Whisper never creates these the trust decision: the one layer Whisper anchors Hard binding: the asset bytes c2pa.hash.data / .bmff · JUMBF-embedded Claim signature COSE_Sign1 · in-band x5chain (RFC 9360) Durable soft binding watermark / fingerprint: survives a screenshot the fence: Whisper stops here Is this signer trusted? today: a curated list · off-list = "unknown source" derive + publish Signer /128 · DNSSEC · DANE-EE 3 1 1 the signer's own zone vouches for the cert serial ✓ Whisper anchors here, no list to join
The manifest, the signature, and the watermark stay theirs. Whisper never touches them. It anchors one layer: the signer-trust decision, made verifiable in public DNS instead of adjudicated by a gate-kept list.

C2PA validator trust config: the keystone

This is the integration everything else builds on, and it is the least invasive one imaginable: it changes no bytes in the manifest and no code in C2PA. The signer certificate stays a normal end-entity X.509 cert, carried in-band as a COSE_Sign1 x5chain exactly as the spec requires. What changes is the validator's trust-anchor set (a list C2PA already treats as a pluggable config input), which now consults one more source: a DANE TLSA record, published under the signer's own DNSSEC-signed domain, that pins the SPKI of that same signer certificate.

The effect is that "is this cert on the central list?" becomes "does the signer's own DNSSEC-signed zone vouch for this serial?": a question anyone can answer against the IANA root, with no list to join, no CA to pay, and no phone-home to a validator vendor. It is, deliberately, the mechanism C2PA's own experimental Web Domain Trust Anchor proposal reached for but did not use: that proposal fetches a self-signed cert from an HTTPS /.well-known/c2pa.json file, and flags domain-takeover and origin-fetch privacy as open problems. A DNSSEC/DANE anchor closes both: the binding is cryptographic to the root, and a resolver, not the origin, serves it.

# The signer cert is unchanged: still a normal COSE_Sign1 x5chain in the manifest.
# The validator gains ONE more trust anchor: a DANE TLSA pin of that same cert,
# published in the signer's own DNSSEC-signed zone. Anyone can pull it.
dig +dnssec TLSA _443._tcp.signer-4a7f2c9e.newsroom.<tenant>.agents.whisper.online
;; flags: qr rd ra ad          ← ad = DNSSEC-authenticated to the IANA root
;; _443._tcp....agents.whisper.online. 300 IN TLSA 3 1 1 b653a4ef...fcb82d1d
;;                                              ↓ SPKI-SHA256 of the C2PA claim-signer EE cert

# The address IS the signer: forward-confirmed reverse DNS names it.
dig -x 2a04:2a01:c0de::51f0 +short
signer-4a7f2c9e.newsroom.<tenant>.agents.whisper.online.

Point a validator's trust config at the DANE source and the full walk confirms itself from the internet's own records, with no central list in the path:

# Verify the signer keylessly and trustlessly: DNSSEC + DANE + a signed ledger entry,
# Whisper's OWN API never trusted. This is the check a validator can run offline.
whisper verify --trustless signer-4a7f2c9e.newsroom.<tenant>.agents.whisper.online
dnssec   pass   DNSSEC-root   AAAA, PTR and TLSA(3 1 1) all DNSSEC-validated to the IANA root
dane     pass   DNSSEC-root   served leaf SPKI-SHA256 == TLSA pin == manifest x5chain leaf
ledger   pass   DNSSEC-root   mint entry present, signature verifies
CRYPTOGRAPHICALLY PROVEN, trust anchor: DNSSEC root (IANA) + DANE-EE

# Or the keyless RDAP object for the address: who holds this signer, since when.
curl https://whisper.online/ip/2a04:2a01:c0de::51f0
!

The one honest caveat, stated plainly. DNSSEC/DANE is not (yet) a formally recognized C2PA conformance trust anchor: today conformance centers on X.509 plus the curated C2PA Trust List. Position a DANE anchor as it truly is: a complementary public identity ecosystem, consumed by a validator that accepts additional trust sources, surfaced through a CAWG identity assertion (below), and offered as a proposal to the standard, never as "already C2PA-approved." The mechanism is legitimate because C2PA leaves trust lists pluggable; the recognition is a roadmap conversation, and we label it as one.

Complements the C2PA Trust List, does not replace it. The Conformance Program and its curated CA anchors stay exactly where they are; a signer on that list keeps its status untouched. Whisper adds a second, domain-owner-controlled anchor for the signers the list does not reach: the independent creator, the stringer, the small newsroom, the AI agent. That is the documented "no Let's Encrypt for provenance" gap. See DANE & TLSA for the byte-for-byte record and Signer & C2PA identity for the derivation.

C2PA manifest COSE_Sign1 · x5chain (EE) serial 4a:72:f9:c… unchanged: no fork publish Signer's DNSSEC zone TLSA 3 1 1 = SPKI-SHA256 of that same EE cert domain owner controls it consult Validator trust anchors pluggable config input + DANE source (one more anchor) trusted signer: no list to join, no CA fee the manifest never changes; only the validator's trust config gains one anchor
The manifest and its x5chain are untouched. The only new artifact is a DANE TLSA pin in the signer's own zone; the only change is that a validator's already-pluggable trust set consults it. Revoke the signer with one op:'revoke' and the pin is gone at DNS-TTL.

CAWG: a did:web issuer root you control

The CAWG Identity Assertion (v1.2, DIF-ratified) is the layer that binds a named human or organization, distinct from the tool or device that signs the claim. A credential holder signs a signer_payload that hash-references the manifest's assertions (including the hard binding), and CAWG defines two credential types: cawg.x509.cose (an end-entity X.509 identity cert, COSE-signed) and cawg.identity_claims_aggregation (a W3C Verifiable Credential from an identity-claims aggregator). Both inherit the same unresolved question CAWG's own documents flag as urgent: who anchors the root?

Two parts of CAWG are already DNS-native, which is what makes the integration clean. First, the ICA credential's issuer is, in real deployments, a did:web, which resolves through DNS + HTTPS, so it is domain-anchored by construction. A Whisper DNSSEC-signed domain is therefore a first-class did:web issuer root: you run your own ICA issuer under a name verifiers trust via DNS, instead of re-centralizing on a third-party aggregator. Second, verifiedIdentities[] carries a cawg.web_site entry whose uri is a domain the actor controls. DANE-bind that URI, bind the cawg.x509.cose organization cert to the same zone, and a credential that was merely cawg.identity.well-formed (valid signature, no root found) becomes cawg.identity.trusted, without an S/MIME CA.

# The ICA issuer is a did:web under a Whisper DNSSEC domain: verifiers resolve it via DNS.
# did:web:newsroom.<tenant>.agents.whisper.online  →  /.well-known/did.json
curl -s https://newsroom.<tenant>.agents.whisper.online/.well-known/did.json | jq '.verificationMethod[0]'
{ "id": "did:web:newsroom.<tenant>.agents.whisper.online#key-1",
  "type": "JsonWebKey2020",
  "publicKeyJwk": { "kty": "EC", "crv": "P-256", "x": "…", "y": "…" } }

# DANE-bind the cawg.web_site.uri + the cawg.x509.cose org cert to the SAME zone.
# "well-formed but unrooted" becomes "trusted", no S/MIME CA in the path.
dig +dnssec TLSA _443._tcp.newsroom.<tenant>.agents.whisper.online +short
3 1 1 c0dea11f...5e0d51f0     ; the org identity cert, pinned in the newsroom's own zone

Complements CAWG, does not replace it. The identity-assertion format, the signer_payload binding, and the aggregator path all stay exactly as CAWG specifies. device_id here is simply the CAWG identity, and the derivation is the same shipped one. Whisper only makes the issuer root and the cawg.web_site domain resolvable and revocable through DNSSEC/DANE, so cross-org trust needs no shared private list. Note the honest limit: a C2PA/CAWG signature is not a qualified eIDAS signature, and DANE recognition in the standard is a roadmap conversation. See did:web & VCs.

The signing pipeline: CAI / c2pa-rs, sign with a DANE-anchored key

This is where the shipped spine attaches. The CAI open-source signing tools, c2pa-rs and its c2pa-python / c2pa-node bindings (the same libraries the ecosystem already uses to build and sign manifests), take a signer's key and end-entity certificate and emit the COSE_Sign1 claim signature. The integration is to sign with a key whose certificate is DANE-anchored under a Whisper domain: the manifest is produced exactly as before, but the signer it references is now self-verifying, with no Trust-List slot and no annual CA fee.

The spine derives that signer's /128 from the key it already holds. Submit the signer's public SubjectPublicKeyInfo and pass the C2PA signer-certificate serial as device_id; the private key never leaves the signing host, only the public SPKI is ever an input. The derivation is deterministic and tenant-bound: the same key and serial always yield the same /128 (idempotent re-provisioning), the mapping is unlinkable to outsiders so a publisher's signers cannot be enumerated from their addresses, the serial alone yields nothing without the key, and the result is DNSSEC-anchored, DANE-EE 3 1 1 pinned, and RDAP-registered the instant it is minted.

# The spine: derive the signer's /128 from the C2PA signing key it already holds +
# its signer-certificate serial (passed as the generic device_id).
CALL whisper.agents({op:'connect', args:{
  tier:                'wireguard',
  identity_public_key: '<base64 SPKI of the C2PA signing key>',
  device_id:           '4a:72:f9:c1:1b:3d:5e:8f:00:11'   // the C2PA signer-cert serial
}}) YIELD op, ok, status, result, error
   RETURN op, ok, status, result, error

Send it with stock tools: the control plane is one HTTP endpoint, the key travels only in the header:

curl -s https://graph.whisper.security/api/query \
  -H "X-API-Key: whisper_live_xxx" \
  -H "content-type: application/json" \
  --data @- <<'JSON'
{"query":"CALL whisper.agents({op:'connect', args:{tier:'wireguard', identity_public_key:'<base64 SPKI>', device_id:'4a:72:f9:c1:1b:3d:5e:8f:00:11'}}) YIELD op, ok, status, result, error RETURN op, ok, status, result, error"}
JSON
{ "op":"connect", "ok":true, "status":"created",
  "result":{ "address":"2a04:2a01:c0de::51f0",
             "fqdn":"signer-4a7f2c9e.newsroom.<tenant>.agents.whisper.online",
             "wireguard":{ /* peer, keys, allowed-ips */ } } }

Then sign as you already do. The CAI pipeline is unchanged; the only difference is the certificate you hand it is the one now pinned in your DNSSEC zone:

# c2pa-rs (or c2pa-python / c2pa-node) signs the manifest exactly as before:
# the signer cert it embeds is the DANE-anchored one. No SDK change.
c2patool photo.jpg --manifest claim.json \
  --signer-cert signer.pem --signer-key signer.key \
  --output photo-signed.jpg
# manifest embedded: c2pa.hash.data (hard binding) + COSE_Sign1 + x5chain(signer.pem)
# the x5chain leaf's SPKI == the TLSA 3 1 1 pin in the signer's DNSSEC zone

The behaviour is honest and predictable, which is the point:

Re-provision with…Result
the same key + serial200: the same /128 returns (idempotent)
a different serial on the same tenant's key409: the identity is already bound; no silent rebind
a non-string serial400: a clear input error, never an opaque 500

Complements the CAI SDKs, does not replace them. c2pa-rs and the CAI tools stay the signing engine; Whisper only supplies a signer certificate whose trust is anchored in public DNS. A dedicated --signer-serial CLI flag is on the roadmap, not shipped: today the serial rides in the generic device_id, which is live. The full derivation, unlinkability property, and DANE mechanics are on Signer & C2PA identity.

Newsroom & camera signers: per-unit revocable device certs

In-camera C2PA signing embeds and signs a manifest at capture with a device or manufacturer signing certificate. That's the first-mile provenance that newsroom procurement increasingly requires, shipping now across professional bodies and the first native smartphones. Each such device is an end-entity claim signer; the holder is a device, and device_id is the device signer-cert serial. The same shape covers a newsroom's own fleet of signing appliances and stringer devices.

The live gap this closes is revocation granularity. In 2025, a camera vendor had to suspend its authenticity service and revoke its entire set of C2PA device certificates after a security vulnerability: a fleet-wide reset because the trust was pinned per-model, not per-unit, and not restored for a long time. Per-unit identities derived from each device's own hardware key, DNSSEC/DANE-anchored and individually revocable, turn that into a one-device, one-call operation: revoke the compromised unit at DNS-TTL and every other camera keeps verifying.

# Provision a per-unit device signer from its hardware key + its device-cert serial.
CALL whisper.agents({op:'connect', args:{
  tier:                'wireguard',
  identity_public_key: '<base64 SPKI of the camera's secure-element key>',
  device_id:           'CAM-8830-A7F2-0C41'          // the device signer-cert serial
}})

# A compromised unit: one call, gone worldwide at DNS-TTL. The rest of the fleet is untouched.
CALL whisper.agents({op:'revoke', args:{agent:'2a04:2a01:c0de::cade'}})
# after the TTL: dig -x returns nothing, verify returns false, for THIS unit only
per-model cert: the 2025 failure mode per-unit DANE identity: one call, one unit one shared device-cert covers the fleet one compromise → fleet-wide revoke, all dark each unit: its own /128 · DANE pin op:revoke one unit at DNS-TTL, the rest keep verifying
A per-model certificate makes a single compromise a fleet-wide outage. A per-unit DANE-anchored identity bounds the blast radius to one device and revokes it in a DNS TTL, the granularity the 2025 incident lacked.

Complements in-camera signing and the device CA, does not replace them. The hardware root of trust stays where it was born: the private key never leaves the secure element, because Whisper consumes only the public SPKI, and the device still signs its manifest exactly as C2PA specifies. Whisper adds an independent, domain-controlled anchor that survives a CA incident and a per-unit off-switch. See Reverse DNS & FCrDNS.

Sign-outputs for AI agents: the agent is its own C2PA signer

When a generative model emits media, the C2PA assertion that marks it is standardized: c2pa.actions with c2pa.created, plus the IPTC digitalSourceType of trainedAlgorithmicMedia (fully synthetic) or compositeWithTrainedAlgorithmicMedia (AI-assisted). What is not settled is who signed it: agent and tool stacks have no path to a Trust-List-recognized signer identity. This is the highest-ceiling integration and the most greenfield, and it maps directly onto Whisper's core: an agent's routable /128 identity is a C2PA signer identity.

The agent signs its own C2PA claim, and where it needs to attest a named creator, a CAWG identity assertion, with a Whisper-anchored, DNSSEC/DANE-verifiable, revocable identity derived from the key it already holds. A verifier confirms the signer against the DNS root with no list membership; a compromised or retired agent signer is revoked in one call at DNS-TTL, closing the exposure window a stolen signing key otherwise keeps open until an optional OCSP check, if ever.

# The per-agent identity is SHIPPED; an agent signing its OWN C2PA claim (sign-outputs) is a proposed pattern.
# Provision the per-agent leaf, then point your C2PA tool's external-signer hook at it.
CALL whisper.agents({op:'connect', args:{
  tier:                'wireguard',
  identity_public_key: '<base64 SPKI of the agent's signing key>',
  device_id:           'agent:renderer-eu-01'          // the agent id, as device_id
}})
  → 2a04:2a01:c0de::a6e7   DNSSEC + DANE live, RDAP-registered
i

Shipped vs proposed, honestly. The per-agent /128 identity, the DANE pin, keyless verify, revoke, and op:lookups are shipped & live. An agent signing its own C2PA claim under that identity (sign-outputs) is a proposed, forward-looking pattern: today you assemble it by pointing your C2PA tool's external-signer hook at the shipped per-agent leaf, and a turnkey Whisper-native sign-outputs helper is on the roadmap. We flag it as forward-looking rather than dress it up as done.

See who verified your content: the loop no one else closes

Because C2PA certificates travel in-band in the manifest, a validator makes no network call to verify a signer. That means a signer has zero visibility into who checked their content, where, or how often. Provenance today is write-only from the signer's side; the only network events are an optional OCSP/CRL fetch and the timestamp authority. This is a genuinely empty quadrant, and a public DNS/DANE anchor fills it as a side-effect.

When a signer identity is DNSSEC/DANE-anchored, a verifier resolving or RDAP-querying that anchor generates lookups against Whisper's own authoritative DNS. op:'lookups' returns who resolved or queried a signer identity's PTR/AAAA/TLSA records and its RDAP object: a verification-analytics stream C2PA structurally cannot provide, and an early-warning tripwire. A spike of resolutions against a signer you retired, or an impostor cert's serial being checked in the wild, shows up here before it shows up as a takedown.

# Who has been resolving / RDAP-querying this signer identity, and when.
# The owner-facing companion to op:'logs' (the signer's OWN activity).
CALL whisper.agents({op:'lookups', args:{agent:'2a04:2a01:c0de::51f0', window:'7d'}})
{ "agent":"2a04:2a01:c0de::51f0", "window":"7d",
  "lookups":[
    {"kind":"TLSA","resolver_asn":"AS15169","count":1840,"first":"…","last":"…"},
    {"kind":"RDAP","source":"verify.example-platform","count":221},
    {"kind":"PTR","resolver_asn":"AS8075","count":96} ] }
today (C2PA): write-only Verifier in-manifest x5chain no network call Signer sees nothing ✕ DANE-anchored: closed loop Verifier Whisper authoritative DNS DNSSEC · TLSA · RDAP resolved resolve signer op:lookups who verified · where · how often
C2PA verification is offline by design, so the signer learns nothing. Anchor the signer in public DNS and the verifier's own resolutions become op:'lookups', the first verification-analytics loop in the provenance stack.

An honest boundary. This sees who resolved the signer anchor, not who looked at your content. A verifier that validates purely offline still generates no signal, and the counts reflect DANE-aware verifiers, not the whole audience. It is an early-warning and adoption signal, not surveillance of every viewer. See Egress governance for the owner-facing surface.

The SIEM feed: Splunk, Sentinel & OpenCTI today

Identity, attribution, and verification analytics are only useful to a trust-and-safety or newsroom-security team if they land in the tools it already runs. Whisper emits every finding as a signed JSON evidence chain: a whisper.identify attribution result naming the operator behind an impersonation host, an op:'lookups' spike against a retired signer, an identity mint or revoke from the transparency log. It is machine-readable enrichment for your existing detection and threat-intel, not another console an analyst babysits.

The Splunk connector is shipped today. It maps each signed finding onto CEF and ECS fields and forwards it into Splunk, so a signer-impersonation tripwire or a rotating-egress attribution becomes a correlatable event beside the rest of your trust-and-safety telemetry:

# The signed finding the Splunk connector maps to CEF / ECS and forwards.
curl -s https://graph.whisper.security/api/query \
  -H "X-API-Key: whisper_live_xxx" \
  -H "content-type: application/json" \
  -d '{"query":"CALL whisper.identify(\"185.12.x.x\")"}' | jq '.result.evidence'
# -> a replayable, signed evidence chain -> CEF: deviceVendor=Whisper cs1=operator …
#    (the connector emits one event per finding; the chain re-verifies offline)

Nothing issued in the dark. Every signer-identity mint and every revoke is written to a public, append-only Merkle transparency log (RFC 6962 tlog-tiles, Ed25519-signed, anchored to Bitcoin via OpenTimestamps): an auditable, non-repudiable issuance-and-revocation trail a publisher, a platform, and a regulator can replay, and a natural evidence base for an EU AI Act Art. 50 disclosure record. Honest status: the log is tamper-evident, signed, and Bitcoin-anchored today, but it is not yet independently witnessed; our two authoritative nodes co-signing is availability, not independence. It already speaks the C2SP tlog-witness protocol, so any external witness can co-sign; that step is on the roadmap and we label it as such rather than overclaim.

The Microsoft Sentinel connector and OpenCTI threat-intelligence integration ship today, mapped onto Sentinel's connector framework and OpenCTI's knowledge graph for continuous-monitoring and disclosure workflows. STIX 2.1 over TAXII export is on the roadmap, not shipped. It is listed honestly under On the roadmap below.

Shipped today vs proposed

Honesty about what runs matters more here than anywhere. The identity primitive under every integration is live and provable right now; the integration guides themselves are proposed patterns for wiring that primitive into each part of the provenance chain. DANE recognition inside C2PA conformance is a proposal to the standard, not a done deal.

Building blockStatus
Deterministic /128 from a signer's public key + C2PA signer-cert serial (as device_id)shipped, live
Provision via the control plane (op:'connect', WireGuard tier)shipped, live
Keyless verify: whisper verify --trustless, /verify-identity, dig -x, RDAPshipped, live
DANE-pin a signer / device / org certificate over DNSSEC (3 1 1)shipped, live
Revoke: /128 + PTR + DANE torn down at DNS-TTL speedshipped, live
Verification analytics & governance: op:'lookups', op:'firewall', op:'budget', policyshipped, live
Attribution graph over the public API (CALL whisper.identify(…))shipped, live
Splunk connector: signed findings mapped to CEF / ECSshipped, live
The integration guides on this page (validator trust config, CAWG, CAI signing, camera/newsroom, agent-signing)proposed, our design, not a vendor endorsement
DANE/DNSSEC as a recognized C2PA conformance trust anchorproposed, complementary today; a proposal to the standard, not approved

A dedicated --signer-serial CLI flag is on the roadmap, not shipped: signer provisioning today goes through the control-plane call shown in the signing-pipeline section above, which is live, with the C2PA signer-cert serial passed as the generic device_id. The CLI verbs that exist are whisper verify --trustless, whisper create --register, whisper kill --revoke, whisper policy, and whisper logs.

On the roadmap

These feed provenance and attribution evidence into the tools a trust-and-safety desk, a newsroom PSIRT, and a compliance team already run. They are roadmap, not yet shipped, listed here so you can see where this goes:

Next