Verify before your agent acts: a trust check for x402 data feeds (in one npm install)
Your agent paid for the data. That doesn't mean the bytes are real. Autonomous agents are starting to pay for data — per call, in stablecoins, over x402. That solves billing. It doesn't solve trust. A 200 OK from a pai
Your agent paid for the data. That doesn't mean the bytes are real.
Autonomous agents are starting to pay for data — per call, in stablecoins, over x402. That solves billing. It doesn't solve trust. A 200 OK from a paid endpoint tells you the money moved. It tells you nothing about whether the bytes you got back are the bytes the publisher actually signed — or whether a proxy, a cache, or a man-in-the-middle rewrote them on the way to your agent.
If your agent is about to act on that data — release funds, open a position, file a report, trigger a workflow — "I paid for it" is not the bar. "I can prove it's intact and I know who stands behind it" is.
This is a 5-minute walkthrough of doing that check before your agent acts, using an open MIT kit. No token, no signup, no API key.
See it first — one command, no wallet
npx @foreseal/demo
This runs entirely offline. It walks an agent through a handful of payloads — a genuine signed one, a byte-tampered one, a forged-key one, one with the receipt stripped off, one with the domain rewritten — and prints ACT or REFUSE for each. The point: a verifier that fails closed. Tampered, forged, or missing-receipt → the agent refuses. No real money is involved; it's the mechanism, distilled.
How the receipt works
Every paid response from the PayPerByte gateway carries an
X-BYTE-Attestation header: an EIP-712 PayloadAttestation.
The recipe is published, machine-readable, at /.well-known/agent.json:
keccak256(responseBody) === payloadHash AND recoverTypedDataAddress(domain, {PayloadAttestation}, message, signature) === attester
Two legs. The hash leg proves the bytes weren't altered. The signer leg proves the receipt was
issued by the key you expect — not self-asserted by whoever sent the response. You check both, locally,
before acting. (One deliberate detail that looks like a bug but isn't: payments settle in USDC on Base, but the attestation's EIP-712 domain is anchored on Arbitrum, chainId 421614. Settlement rail and trust anchor are intentionally separate.)
Wire it into your agent
npm i @payperbyte/sdk@^0.1.2
Version matters: the full hash and signer check (
verify/verifyAttestation/
verifyFromGatewayResponse) ships in 0.1.2+. Earlier0.1.0/0.1.1only export the hash-only
verifyPayload. Pin^0.1.2.
The one-call path, for a response you just fetched from the gateway:
import { verifyFromGatewayResponse, ARBITRUM_SEPOLIA } from '@payperbyte/sdk';
// Pin this once from https://x402.payperbyte.io/.well-known/agent.json → receipt.attester
const GATEWAY_ATTESTER = '0x77c86a5367d941091a31BC97104609F2Db33C472';
async function getVerifiedData(url: string, res: Response): Promise<Uint8Array> {
const body = new Uint8Array(await res.arrayBuffer());
const header = res.headers.get('X-BYTE-Attestation');
const verdict = await verifyFromGatewayResponse(
body,
header,
ARBITRUM_SEPOLIA, // the attestation domain's chain (421614)
GATEWAY_ATTESTER, // pin it — a self-asserted header can't prove itself
);
if (!verdict.verified) {
// fail-closed: tampered, forged, expired-key, or no receipt at all
throw new Error(`refusing to act on ${url}: ${verdict.reason}`);
}
return body; // safe to act on
}
That GATEWAY_ATTESTER argument is not optional ceremony. If you don't pin the attester, the kit
fails closed on purpose — because the publisher field inside an attestation header is attacker- controlled, so trusting it would let a forged response self-certify. Pin the address from the published recipe; then a forged header has nothing to stand on.
The Verdict you get back is explicit, so your logs tell you why something was refused:
interface Verdict {
verified: boolean; // hashMatch && signerMatch === true — the one "safe to act?" bool
hashMatch: boolean; // bytes weren't altered
signerMatch: boolean | null; // null = no attestation present → fail-closed (never "pass on hash alone")
recovered: string | null; // the address the signature actually recovers to
expired: boolean; // ADVISORY ONLY — staleness is a freshness axis, not a provenance verdict
reason: string; // human-readable, for post-mortems
}
Note expired does not flip verified to false. A once-minted now+300s deadline makes every aged feed look "expired"; that's a freshness question for your own policy, not a tamper verdict. Surface it, decide for yourself.
If you're working from the lower-level pieces (raw bytes + the attested fields, or an on-chain event
rather than a gateway header), verify(input) takes the explicit struct, and verifyPayload does the hash-only leg on its own. Same fail-closed contract: it throws nothing and always returns a Verdict.
Bonus: verify the counterparty, not just the payload
The same EIP-712 receipt pattern backs a live $0.05 counterparty screen — a signed go/no-go on an address+domain before your agent releases funds. It's a paid feed; the verdict comes back as a signed ALLOW/WARN/BLOCK you verify the same way:
curl -i -X POST https://x402.payperbyte.io/feeds/address-reputation \
-H 'content-type: application/json' \
-d '{"domain":"payee-checkout-7x9q.example","address":"0xRecipient","amount":5000000,"chain":"base"}'
(With no payment header you get the HTTP 402 challenge back — that's the handshake, not an error. This is a counterparty screen — "is it safe to send here?" — not a seller-reputation score.)
Where this is honest about itself
This is early. It's dogfooded end-to-end; the only mainnet settlements so far are our own self-tests, and external adoption is exactly the open question we're publishing this to answer. What it does prove is narrow and real: authenticity and tamper-evidence, not correctness — your agent learns "these are exactly the bytes that were signed, by the key I expected," not "this data is true." It's MIT, USDC-only, no token, no new contracts to deploy. The verifier is the whole pitch: a small amount of code, hardened to fail closed, that you run before your agent acts.
- Demo:
npx @foreseal/demo - Kit:
npm i @payperbyte/sdk@^0.1.2· Gate (for publishers):@foreseal/gate - Recipe:
https://x402.payperbyte.io/.well-known/agent.json - MCP server (drive it from Claude/Cursor):
npx -y byte-mcp-server
Disclosure: I build PayPerByte (machine name "BYTE Library").
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.