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 by generate_webmcp.py and 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/transactions requires 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 parses rel="giving", and ChatGPT's browser provides no document.modelContext so 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. once works 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_destinations stays 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_prepare never charges. It returns payment_completed: false and requires_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, DonateAction with an EntryPoint.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.txt needs 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, and ads.txt is 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.

Built With

Share this project:

Updates

Submission history