Inspiration
A nonprofit should decide where its own donations go. This is going to matter as AI Agents begin to take over web actions like donating.
Tell an AI agent "donate $50 to this nonprofit" and it routes to whatever it can parse. That is structurally almost never the nonprofit's own donation page. It's an aggregator, an outdated page, or a third-party fundraising page set up on the organization's behalf without its knowledge. Those pathways can hold funds for weeks before disbursing — some intermediaries take up to 90 days — introducing float, intermediary risk, and a donor record the nonprofit never sees. The organization gets the money once and loses the relationship.
Two trends are making this worse at the same time:
- Agents are being locked out. Cloudflare challenges, bot rules, and hardened forms mean the legitimate path is often the hardest one for an agent to complete — so it takes the easy path, which is somebody else's page.
- Donation forms are unreadable by design. Most are cross-origin iframes. An agent looking at a real nonprofit donate page cannot see the amount, the frequency, or who receives the money. It's a black box, so it guesses.
The result is an agent doing the reasonable thing with bad information. We wanted the nonprofit to be able to state, in a way a machine can act on, this is where our money may go — and have that statement come from the one domain nobody else controls.
What it does
The Verified Giving Protocol lets a nonprofit publish a machine-readable declaration at
https://their-domain.org/giving.json naming the giving destinations it authorizes — and
exposes that to AI agents as WebMCP tools.
The protocol turns on one distinction:
| Statement | Who makes it | Authorizes? |
|---|---|---|
| benefits — "proceeds go to Organization X" | anyone | no |
| confirmed — "evidence consistently identifies this destination" | a discovery process | no |
| authorized — "we declare this an approved destination" | the organization | yes |
No amount of evidence converts confirmed into authorized. A human at the nonprofit types a
fixed affirmation verbatim, and that's the only route in. It's enforced as a const in the
JSON Schema, in the Python approval script, and in the WebMCP tools — not as a UI check. A
paraphrase is refused.
The nonprofit's own page then registers four WebMCP tools built entirely from that declaration:
giving_verify, giving_options, giving_designations, and giving_prepare. An agent asks
instead of scrapes.
The reference implementation is live. Power Poetry (https://www.powerpoetry.org/donate) publishes a declaration naming To Be Heard Foundation Inc, EIN 13-3648963, verified against the IRS Business Master File. An agent starting from the donate page alone can determine who legally receives the money, that the organization authorized it, and construct a working prefilled donation URL.
How we built it
- The standard — a specification, a JSON Schema, and a stdlib-only Python validator and approval script. No dependencies, so a nonprofit can run the tooling without a toolchain.
- The WebMCP layer —
giving-tools.js, generated from a reviewed template bygenerate_webmcp.pyand byte-compared against a fresh generation in CI. The tools cannot drift from the template, and hand-editing them to say something the declaration doesn't is caught by the test suite. - The site — verifiedgiving.ai, an Astro build with an in-browser generator and a live validator. The site pins the standard as a git submodule and renders the specification and publishes the JSON Schema from it at build time, so no copy can drift from the source.
- The discovery agent —
vgpify, a Claude skill that crawls a nonprofit's site, extracts organization identity and giving pathways, flags conflicts, and produces an evidence-backed review document. It proposes; it cannot publish. - 54 tests, and most of them assert refusals rather than successes.
Challenges we ran into
There is no payment API to route to. We assumed one existed. Givebutter — the processor behind our reference implementation — publishes 42 API endpoints and none of them creates a checkout session, payment intent, charge, or token.
POST /v1/transactionsrequires a past timestamp and has no payment-instrument field: it records a gift that already happened. The capability exists one layer down (Givebutter settles through Stripe), but no nonprofit-facing platform exposes it. We're now in conversation with people interested in changing that, and published an open request (https://verifiedgiving.ai/providers) describing exactly what we'd need and — importantly — what we would not ask a processor to take on.Publishing is not discovery. We deployed a correct declaration, at the correct path, with the correct media type, plus the link relation and the WebMCP script — then pointed ChatGPT at the page. It found almost nothing. Every discovery mechanism we'd built required the world to have already adopted VGP: nobody probes
/giving.json, nothing parsesrel="giving", and ChatGPT's browser provides nodocument.modelContextso the tools never register. That five-minute test reshaped the project.The cold start. VGP has one adopter and zero consumers. A standard nobody consumes is invisible, and a nonprofit that does everything right and sees no effect concludes the standard doesn't work. That's the likeliest way this dies, and we'd rather say so than imply an ecosystem that doesn't exist.
Platform documentation isn't platform behaviour. Givebutter documents
frequency=quarterly; it's not enabled on our campaign and is silently ignored.onceworks and is undocumented. Amounts over 999 are silently dropped from prefill. Every one of those was found by testing the live widget, and each would have failed in the worst place — a donor believing they'd set up a monthly gift that wasn't.
Accomplishments that we're proud of
- The tools fail closed, and it's provable. If a declaration is missing, unapproved, or
wrongly served, the page registers no giving tools at all. An agent is told "this site
exposes no authorized giving pathway" rather than handed a plausible form. Flip one boolean and
watch it happen:
node --test webmcp/harness.test.mjs. - Authorization cannot be faked, and you can check in ten seconds. In the live generator,
type a paraphrase of the affirmation —
authorized_destinationsstays empty. Type it exactly — the destination is authorized. - A live reference implementation, end-to-end verified: an agent derives the receiving legal entity, its EIN, the authorization record, and a prefilled donation URL that drives the real payment widget to the right state.
giving_preparenever charges. It returnspayment_completed: falseandrequires_human_payment_authorization: true. VGP routes; the donor authorizes; the nonprofit's existing processor charges.- We shipped the honest version. The site states its own adoption position rather than implying consumers exist.
What we learned
- Absence of a tool is safer than presence of an ambiguous one. The most valuable thing our WebMCP integration does is decline to appear.
- Infrastructure ahead of its consumers is invisible, not broken — and the escape is to speak
a vocabulary that already has consumers while the new one accumulates them. We added schema.org
(
legalName,taxID,nonprofitStatus,DonateActionwith anEntryPoint.urlTemplate) and a plain visible link to the declaration. That turned "a couple more details" into an agent that could name the receiving entity and build a working donation URL — with no change to the underlying declaration. - Rank discovery mechanisms by how little the world must change: a visible link needs nothing;
schema.org needs a vocabulary that already has consumers;
llms.txtneeds a spreading convention; your own new mechanism needs adoption. Ship them in that order. - The declaration belongs at the document root, not
/.well-known/./.well-known/is for protocol metadata; the root is for owner policy —robots.txt,sitemap.xml,ads.txt. VGP is the second kind, andads.txtis the near-exact analogue: a domain owner naming who may act on their behalf, published to defeat unauthorized intermediaries. The test is what absence means. - Declare, never infer. Every platform parameter is declared by the organization with an explicit vocabulary and bounds. A guessed parameter fails silently where it costs most.
What's next for Verified Giving Protocol
- A payment endpoint worth declaring. We've specified what we'd need from processors: a checkout session an agent can create, a required idempotency key (agents retry, and a dropped response on a donation charges someone twice), published amount and currency rules, status and receipt — and explicitly nothing about who is authorized, because that isn't a processor's question to answer.
- Consumers. The protocol needs agents that look for it. WebMCP support in a shipping client is the unlock.
- Platform profiles, so Givebutter, Classy, Every.org, Bloomerang and Blackbaud each publish their vocabulary once instead of every nonprofit hand-writing it. We are in touch with some of these.
- A registry of publishing organizations, so agents have somewhere to look before crawling the web — pending the governance question of who may list and what a listing asserts.
- More adopters. One is a proof; it isn't a standard yet. -Vendor partners. Work with donation platforms to create endpoints that could help agents process donations directly. -Industry partners. Leverage networks like the Nonprofit Technology Network NTEN to promote.
Log in or sign up for Devpost to join the conversation.