Skip to content

Evidence helpers

Evidence is the network’s own proof, attached to an event. The helpers in @halvorai/ingest/evidence do two things only: put each proof into the right shape, and compute its SHA-256 digest. They do not verify anything. Verification happens on the server, so it is the same for every producer. See evidence and verification.

import { ap2, vi, tap } from '@halvorai/ingest/evidence';
halvor.emit({
type: 'payment.authorized',
payment: { /* ... */ },
payee: { /* ... */ },
evidence: [ap2.paymentMandate(paymentMandateSdJwt), ap2.checkoutMandate(checkoutMandateSdJwt)],
});

ap2.paymentMandate(sdJwt) returns { kind: 'ap2.payment_mandate', format, value, sha256 }. The format is kb-sd-jwt when the string ends with a key-binding token and sd-jwt otherwise. ap2.checkoutMandate does the same for checkout mandates. Pass the whole tilde-separated string exactly as you received it.

evidence: [vi.chain(presentationString)]

Pass the full chain of layers as one string. Do not split or reorder it, because the digest and the signatures depend on the exact bytes.

app.post('/checkout', (req, res) => {
halvor.emit({
type: 'payment.authorized',
payment: { /* ... */ },
payee: { /* ... */ },
evidence: [tap.fromRequest(req)],
});
});

tap.fromRequest(req) reads the Signature-Input and Signature headers and the signed components, such as the authority and the path, from the incoming request. It builds the evidence object that the verifier needs to check the signature against Visa’s key directory.

The digest on an evidence item is over its exact bytes. A bank can recompute it from the pack and see that the value you stored is the value you sent. If you reformat a credential before sending it, the digest still matches the new bytes, but the credential’s own signature no longer verifies, and the item becomes invalid. Send credentials untouched.

For other kinds, such as x402 payloads and Stripe snapshots, the rail adapters attach the evidence for you. To attach your own JSON, set kind, format: 'json', the value, and a sha256 computed over its RFC 8785 canonical form. The SDK recomputes the digest after it redacts personal data from JSON evidence.

Signed credentials (sd-jwt, kb-sd-jwt, jws) are never redacted, so their signatures stay valid.