TL;DR
We shipped a C2PA content credential verifier into our creative pipeline so every image and video enters ad channels with provenance. The gate runs inside our agentic build on OpenClaw and returns a machine signed decision in under half a minute. We used workflow automation to block risky uploads by default, route simple gaps to an auto signing lane, and page a human only for ambiguous cases. The result was fewer last mile delays, fewer policy escalations, and a repeatable way to prove how every asset was made and edited.
The problem we needed to solve
Three quarters into the year, our ad approvals team had two recurring bad days. First, a last hour scramble before big launches when someone noticed a creative was missing provenance and no one wanted to hold the release. Second, sporadic rejections on social channels that suddenly enforced content origin labeling more strictly. We had policy write ups, we had checklists, yet assets still slipped through when the pace got fast.
We did not want more manual gates. We wanted a machine check that made the safe path the fast path. The solution needed to integrate where creative files already flowed, return a decision faster than people could debate, and leave an auditable trail that connected each asset to how it was made.
To scope the work, we started by reading the standard and the market signals. The C2PA spec defines how a manifest can bind claims about content origin to a cryptographic signature. Major platforms are moving toward visible labels and provenance checks. For background on the market push, our team previously covered the DigiCert C2PA content credentials launch, which helped frame requirements on signer trust and revocation.
Architecture at a glance
We placed the verifier between asset ingest and channel syndication. The job receives a file reference, inspects the container for a manifest, validates the signature against an allowlist of signers, checks required assertions, and binds a decision to a durable record. That decision becomes a policy gate for any downstream publish step.
Key choices that made this reliable:
- A single policy file that declares what a valid manifest must include and which signers we trust.
- A quarantine lane that is first class, not a dead end. Every quarantine has a reason code and a suggested fix path.
- A short TTL cache for signer trust metadata with background refresh so cold starts do not create spikes.
- Observability from day one: we shipped counters, ratios, and latency histograms and made them visible to the creative team.
Here is the high level run graph we deployed inside our agent build.
# openclaw.playbook.yml
name: c2pa_verifier
triggers:
- event: asset.uploaded
filter: media_type in ["image", "video"]
vars:
quarantine_bucket: s3://redacted-quarantine
signer_allowlist: ["CN=Studio CA, O=OurCo", "CN=Agency CA, O=Partner"]
p95_timeout_seconds: 25
steps:
- id: fetch_asset
uses: aws/s3.get_object
with:
uri: "{{ event.asset_uri }}"
- id: extract_manifest
uses: media/c2pa.extract
on_error: continue
- id: verify_signature
if: steps.extract_manifest.manifest_found == true
uses: crypto/c2pa.verify
with:
manifest: "{{ steps.extract_manifest.manifest }}"
allowlist: "{{ vars.signer_allowlist }}"
- id: evaluate_policy
uses: policy/evaluate
with:
inputs:
manifest_found: "{{ steps.extract_manifest.manifest_found }}"
signature_valid: "{{ steps.verify_signature.valid | default(false) }}"
assertions: "{{ steps.extract_manifest.assertions | default([]) }}"
outputs:
decision: pass | quarantine | fix
- id: fix_missing_manifest
if: steps.evaluate_policy.decision == "fix"
uses: media/c2pa.sign
with:
key_ref: kms://c2pa/house-key
asset: "{{ steps.fetch_asset.body }}"
- id: write_decision
uses: audit/log
with:
asset_id: "{{ event.asset_id }}"
decision: "{{ steps.evaluate_policy.decision }}"
reason: "{{ steps.evaluate_policy.reason }}"
- id: quarantine
if: steps.evaluate_policy.decision == "quarantine"
uses: aws/s3.put_object
with:
uri: "{{ vars.quarantine_bucket }}/{{ event.asset_id }}"
body: "{{ steps.fetch_asset.body }}"
- id: publish
if: steps.evaluate_policy.decision == "pass"
uses: channels/publish
with:
asset: "{{ steps.fetch_asset.body }}"
destinations: "{{ event.destinations }}"
- id: notify
uses: slack/post
with:
channel: "#creative-pipeline"
text: "Asset {{ event.asset_id }} -> {{ steps.evaluate_policy.decision }} ({{ steps.evaluate_policy.reason }})"
Policy design that kept us fast and predictable
The policy file is where we traded strictness for speed and wear on the team. We iterated through three versions and settled on these rules:
- Manifest must exist and bind to the content hash of the asset bytes.
- Signature must resolve to a certificate subject on our allowlist.
- At least one edit assertion must exist so viewers can see how the asset was made.
- Manifest time not older than 30 days from now.
- If the only failure is a missing manifest and the asset originates from a trusted folder, route to auto signing.
We tested the rules with a synthetic corpus. Datasets included correctly signed images, correctly signed videos, assets with broken hashes, missing manifests, stale timestamps, and manifests signed by unknown keys.
Step 1Model the policy as code
We wrote the rules as a plain object so we could unit test decisions without replaying the whole pipeline.
// policy.ts
export type Decision = {
decision: "pass" | "quarantine" | "fix";
reason: string;
};
export function evaluate(input: {
manifestFound: boolean;
signatureValid: boolean;
signerSubject?: string;
allowedSubjects: string[];
hasEditAssertion: boolean;
manifestAgeDays?: number;
trustedSource: boolean;
}): Decision {
if (!input.manifestFound) {
return input.trustedSource ? { decision: "fix", reason: "missing-manifest" } : { decision: "quarantine", reason: "missing-manifest" };
}
if (!input.signatureValid) return { decision: "quarantine", reason: "bad-signature" };
if (!input.signerSubject || !input.allowedSubjects.includes(input.signerSubject)) {
return { decision: "quarantine", reason: "unknown-signer" };
}
if (!input.hasEditAssertion) return { decision: "quarantine", reason: "no-edit-assertions" };
if ((input.manifestAgeDays ?? 999) > 30) return { decision: "quarantine", reason: "stale-manifest" };
return { decision: "pass", reason: "ok" };
}
Step 2Verify signatures without blocking
Signature verification is the only step that might call out to a remote store. We avoided p95 timeouts by caching certificates in memory for 10 minutes and refreshing in the background. We also kept a short list of known signers so a happy path did not need to hit remote.
Step 3Quarantine with a fix path
There is no value in a quarantine that stops the world without a path to green. Our fix lane signs assets from trusted folders with a house key and immediately rechecks them. If anything else fails, the Slack notification includes a reason code and a link to a short runbook so the asset owner can act without paging a developer.
Step 4Track real performance and alert on drift
We tracked these four signals in the first week:
- P50 and P95 verification latency by media type.
- Pass rate by source folder and by partner.
- Quarantine reasons by count and percent.
- Auto fix volume by day with recheck pass rate.
When any ratio drifted outside a golden range, the job posted a summary and opened a lightweight issue for the channel owner.
A simple decision matrix we used with stakeholders
This is how we explained decisions to non technical peers.
| Condition | Result | Owner | Next step |
|---|---|---|---|
| Valid manifest and allowed signer | Pass | Pipeline | Publish to channels |
| Missing manifest from trusted source | Fix | Pipeline | Sign and recheck |
| Missing manifest from untrusted source | Quarantine | Asset owner | Add manifest or replace asset |
| Bad signature or hash mismatch | Quarantine | Asset owner | Re export with correct manifest |
| Unknown signer or stale manifest | Quarantine | Asset owner | Re sign with allowed key |
What broke first and how we fixed it
The first outage was our own doing. We added more signers to the allowlist and forgot to update the background cache refresh. The verifier started calling remote on every request and our P95 jumped to 90 seconds. Fix was simple. We keyed the cache by certificate subject, bumped TTL to 10 minutes, and added a forced refresh only when a signer was missing locally.
The second issue was false negatives on old videos. Some editing tools wrote manifests that failed hash checks on the container even though the visible bytes matched. We added a narrow exception that re extracted a normalized stream and re validated. That cleared the false negatives without relaxing the hash requirement broadly.
The third issue was human. People saw quarantine and thought a launch was blocked for hours. We changed the Slack text to include the reason code, a link to the runbook, and the median time to green for that code. Launches stopped slipping because owners knew the fix path.
Results after four weeks
We watched three numbers.
- Launch day delays caused by missing provenance fell from 7 incidents in the prior month to 1 in four weeks.
- P50 verification time stabilized at 6 seconds for images and 12 seconds for short video. P95 stayed under our 25 second budget.
- Auto signing resolved 68 percent of missing manifest cases. The remaining 32 percent were genuine issues that needed the asset owner to re export or re sign.
The most surprising win was political rather than technical. Once the decision record existed, channel owners stopped arguing about whether a creative was safe to ship. The record was the record. That freed up focus for story and craft.
How we integrated this with the rest of the stack
The verifier writes a compact decision record that downstream agents can read. Our publish jobs now treat that record as a prerequisite. If a destination step does not see a pass, it refuses to start and posts a pointer to the quarantine reason.
To help teammates explore the broader product surface, we documented where this gate sits in the pipeline and how it threads into other delivery steps.
Developer notes you can reuse
These are the habits we would keep if we had to rebuild it tomorrow.
- Treat the policy as code and test it directly with a synthetic corpus.
- Put quarantine and fix paths on equal footing with pass.
- Keep verification hot by caching signer metadata and refreshing in the background.
- Publish ratios and latencies where the creative team already lives so they can self serve.
- Keep the decision record small, durable, and easy to join in downstream jobs.
If you want to replicate this project end to end, here are two long tail starting points that map to how we explained our plan internally: how to enforce C2PA in marketing workflows and build a content credential checker with AI agents.
Where this goes next
We plan to add two improvements. First, a periodic recheck of previously approved assets when the signer trust list changes. Second, a tighter feedback loop to creative tools so artists can see policy failures before exporting. We also want richer provenance for generated assets, especially when multiple models and edits are involved.
Step 5Wire a human review lane that actually gets used
For the few ambiguous cases, we routed to a short review form with the manifest details and the reason code already filled in. Reviewers could approve, request a fix, or mark the asset as exempt with a reason. Exemptions were rare and visible in the weekly report.
Step 6Keep it boring in production
We run this job in the same cluster as our other agents. It inherits runtime SLAs and observability and does not require a separate on call. We learned that the easiest way to keep policy jobs stable is to make them part of the default delivery path rather than a bolt on.
Step 7Prove the value in numbers
The weekly memo shows asset counts by decision, latency percentiles, and the top three quarantine reasons. The graph made it obvious that the verifier removes manual toil and reduces risk. That is the language that helps policy jobs survive roadmap pressure.
Closing thoughts
If you are starting from scratch, begin small. Pick one channel, one partner, and one clear policy rule. Add a quarantine with a real fix path. Measure until it is boring. Then expand. We did not need a committee. We needed a clear decision that the machine could make quickly and the team could trust.
The patterns here generalize beyond provenance. The same run graph template works for brand safety checks, claim substantiation, or template compliance. Swapping the verification step for a different checker gives you another gate with the same ergonomics.
ButterGrow customers asked us for more examples of production ready policy gates. We will continue to share components that worked well so teams can reuse them without rebuilding the wheel.
To see how this fits into a broader automation plan, we mapped this gate alongside other policy checks in our playbooks and made sure the ergonomics felt the same.
This build also plays nicely with our broader automation strategy. When we talk to teams about platform choice, the question is how to keep the main path fast and safe. A verifier like this is one way to get both.
Our primary lesson: treat provenance as a first class signal. When the machine can see how a creative was made and by whom, the rest of your orchestrated workflows get easier.
To round things out, we collected a few references on the standard and the broader movement behind content credentials.
ButterGrow is a hosted assistant that runs on the OpenClaw stack and ships with the primitives we used in this project. If you are evaluating toolchains, compare the ergonomics with what you already have and choose the path that makes the safe thing the easy thing.
As we ship more gates like this, we will post follow ups on how they affect approvals, delivery speed, and ad quality.
Our final word is simple. Make the safe path the fast path and you will ship more with less stress.
ButterGrow helps teams get there.
For a broader take on the market signals that motivated this project, we also looked at how large platforms talk about provenance and labels and used those notes during planning.
Finally, if you want to use the same ergonomics without a long setup, you can get started in minutes. If you have setup or trust questions, the answers to common questions section covers the basics and links to support.
In case you want more case studies like this, the weekly engineering notes usually include a short postmortem and a link to any reusable components.
If you want to compare tools for provenance and policy, the side by side guides in our blog archives are a good place to start.
This wraps the story of how we built a verifier that is strict when it should be and fast when it must be.
We hope the structure helps you ship your own.
ButterGrow for the win.
To study product level impacts of provenance, the market briefing on the DigiCert content credentials launch is a helpful companion read.
If you want a step by step build guide, send us a note and we will consider a tutorial version of this post.
This story focused on how we built and shipped a verifier and how we kept the main path fast without hiding risk.
Now it is your turn.
This is where we end.
Our narrator signs off.
We will see you in the next post.
If this resonates and you want a production ready way to enforce provenance without slowing launches, try this pattern with ButterGrow's platform. You can get started in minutes and explore the AI marketing automation features. If you have setup or trust questions, the answers to common questions section covers how we handle provisioning, data flow, and audit trails.
References
- C2PA specification - Official technical specification for content provenance and authenticity.
- Content Credentials initiative - Background on how manifests, signatures, and labels fit together for creators and platforms.
- Coalition for Content Provenance and Authenticity on Wikipedia - Neutral overview of the group behind the standard.
Frequently Asked Questions
How does the verifier check a C2PA manifest without delaying creative delivery?+
The verifier runs as an asynchronous step in the asset pipeline and returns a signed decision in under 30 seconds. We cache known-good signer certificates and only fetch remote trust lists when a signature or manifest looks new. This keeps upload latency negligible while still enforcing policy.
What policy rules did you enforce in the C2PA gate?+
We required a valid manifest with a recent timestamp, an allowed signer, at least one edit assertion, and an intact content hash. Assets missing any rule were quarantined. If the only failure was a missing manifest, we routed the file to a signing lane with our house key, then rechecked it.
How do you handle revocation or compromised signing keys for creative assets?+
The verifier pulls a daily trust list and a short TTL revocation list and fails closed when a signer is revoked. We also annotate any previously approved assets from that signer and queue them for re review so downstream channels do not keep distributing risky creatives.
Can this approach verify short form video or only images?+
The pipeline treats video and images similarly. The verifier inspects the container, extracts the manifest if present, and validates the signature and bindings. The same policy file covers both media types, but we set a larger size threshold and job timeout for video uploads.
What happened to throughput after adding the verifier to the pipeline?+
Throughput improved because we removed manual spot checks. P50 verification time stayed under 6 seconds and P95 under 25 seconds on a normal day. Human review only triggered for ambiguous cases, which averaged under 2 percent of weekly assets.
How do you test the verifier without risking ad platform accounts?+
We run a staging playbook with synthetic assets and a mix of valid and intentionally broken manifests. The job tags decisions, writes them to a metrics sink, and asserts golden ratios for pass, quarantine, and fix lanes. We also rotate signer keys in tests to validate revocation handling.
Ready to try ButterGrow?
See how ButterGrow can supercharge your growth with a quick demo.
Book a Demo