Draft — Not yet published. This specification is in active development and is not ready for review or critique. Stay tuned for formal announcements.

Proof of Payment
for ATProtocol

An open specification for decentralized, cryptographically verifiable proof of payments.

Built on badge.blue ATProtocol

Documentation

Guides for every role in the attested payments ecosystem.

Overview

Payment proofs replace centralized payment databases with cryptographic attestation records distributed across ATProtocol repositories. Any application can independently verify a payment—no single platform controls the relationship.

Three-party attestation

Every payment creates records across three independent repositories. The supporter declares intent, the creator confirms receipt, and a broker—which facilitates and witnesses the exchange—writes its own proof. Each record is content-addressed and bound to its repository, preventing replay attacks.

graph LR
  subgraph SR["Supporter's Repo"]
    PAY["Payment Record
oneTime | recurring | scheduled"] SIG["signatures: [strongRef, ...]"] end subgraph CR["Creator's Repo"] CP["payment.proof
Attests via CID"] end subgraph BR["Broker's Repo"] BP["payment.proof
Attests via CID"] end SIG -- "strongRef" --> CP SIG -- "strongRef" --> BP

Data Ownership

Each party controls their own records in their own repository. No single entity holds the full picture.

Cryptographic Integrity

CID-based content addressing ensures records cannot be modified after attestation. Change the data, break the hash.

Portable Relationships

Support relationships survive platform changes. Records live on ATProtocol’s network, not in proprietary databases.

Open Broker Model

Any entity can serve as a broker because brokers facilitate payment. A broker might process Stripe transactions, handle peer-to-peer cash, or simply witness a virtual high-five. The role is to facilitate and witness the exchange between payer and recipient.

This spec builds on badge.blue’s CID-first attestation framework. Every attestation CID is computed from the record content, metadata, and the repository DID—meaning a record copied to a different repository automatically invalidates all its attestations.

Public & private proofs. Payment records and proofs can live in a user’s public repository for open verification, or within a Permissioned Data Space for private access. The implementation is effectively identical in both cases—the same attestation mechanics, CID binding, and strongRef signatures apply. The only difference is that the payment servicer additionally manages creation of the permissioned space in the payer’s repository and ensures all parties (creator, broker) have appropriate read access and permissions.

Payment Intent

When a recipient wants to accept a payment, a structured discovery and initiation flow connects payer, recipient, and payment servicer:

  1. The recipient signals which payment servicers they use by publishing an ordered list of DIDs that have a #AttestedNetwork service endpoint in their DID document
  2. The payer’s client resolves these DIDs and presents the available servicers. The payer selects which one to use
  3. The payer’s client makes an authenticated request (using inter-service authentication) to network.attested.payment.initiate on the selected servicer, passing the product identifier. The response includes a token and a URL
  4. The payer is directed through a browser to the URL to complete the payment process (entering payment details, confirming terms, etc.)
  5. The payer’s client polls network.attested.payment.status with the token. The response is either a strongRef pointing to the completed payment record, or a failed status
sequenceDiagram
    participant P as Payer's Client
    participant R as Recipient
    participant S as Payment Servicer
    participant B as Browser

    P->>R: Resolve DID document
    R-->>P: Service endpoints with #AttestedNetwork DIDs
    P->>P: Select servicer from list

    P->>S: network.attested.payment.initiate (authenticated)
    Note right of P: Sends product identifier
    S-->>P: { token, url }

    P->>B: Redirect payer to URL
    B->>S: Complete payment flow
    S-->>B: Payment confirmed

    P->>S: network.attested.payment.status (token)
    S-->>P: { strongRef } or { failed }
      

Verification

Any application can verify a payment by checking the cryptographic chain:

  1. Strip the signatures array from the payment record
  2. Prepare attestation metadata—add the repository DID, strip cid and signature fields
  3. Insert metadata as the $sig field, serialize to DAG-CBOR
  4. Hash with SHA-256 and wrap as CIDv1 (codec 0x71)
  5. Fetch proof records via strongRef URIs and confirm CIDs match

Trust Models

Applications can implement different validation strategies:

Lexicon

Four record types and three XRPC methods define the payment specification. Payment records live in the supporter’s repository and carry a signatures array referencing proof records from creators and brokers.

Record
network.attested.payment.oneTime

A one-time payment from a supporter to a creator. Represents a single, non-recurring financial transaction attested by one or more parties.

FieldTypeDescription
subjectstring (did)DID of the creator receiving payment
amountintegerPayment amount in smallest currency unit (e.g. cents). Min: 1
currencystringISO 4217 currency code (e.g. USD, EUR)
txnidstringUnique transaction identifier for deduplication
memostringOptional note from the supporter. Max 256 chars
createdAtstring (datetime)Timestamp of record creation
entitlementsarray(com.atproto.repo.strongRef)Optional list of strong references to records representing any goods or services the payer is entitled to as a result of this payment
signaturesarrayAttestation entries (inline or com.atproto.repo.strongRef)

Entitlements. The entitlements field is an optional array of com.atproto.repo.strongRef objects. Each reference points to a record representing a good or service the payer is entitled to as a result of this payment. The referenced records can use any lexicon—they are not defined by this spec. This allows brokers, recipients, or third parties to define their own product or access records and link them directly to the payment that granted them.

Record
network.attested.payment.recurring

A recurring payment commitment. Immutable once created—supporters cancel and create new subscriptions to change terms. Bills on anniversary dates with automatic retry on failure.

FieldTypeDescription
subjectstring (did)DID of the creator receiving payment
amountintegerAmount per billing period in smallest currency unit. Min: 500, Max: 25000 (monthly equivalent)
currencystringISO 4217 currency code
unitstringBilling period: monthly | quarterly | semiannual | yearly
frequencyintegerBilling frequency multiplier. Default: 1, Min: 1
txnidstringUnique transaction identifier for deduplication
createdAtstring (datetime)Timestamp of initial subscription creation
entitlementsarray(com.atproto.repo.strongRef)Optional list of strong references to records representing any goods or services the payer is entitled to as a result of this payment
signaturesarrayAttestation entries (inline or com.atproto.repo.strongRef)
Record
network.attested.payment.scheduled

A fixed series of payments. Unlike recurring payments that continue indefinitely, scheduled payments specify a total count and terminate automatically upon completion.

FieldTypeDescription
subjectstring (did)DID of the creator receiving payment
amountintegerAmount per payment in smallest currency unit
currencystringISO 4217 currency code
unitstringInterval: monthly | quarterly | semiannual | yearly
countintegerTotal number of scheduled payments. Min: 2, Max: 60
txnidstringUnique transaction identifier for deduplication
createdAtstring (datetime)Timestamp of schedule creation; first payment date
entitlementsarray(com.atproto.repo.strongRef)Optional list of strong references to records representing any goods or services the payer is entitled to as a result of this payment
signaturesarrayAttestation entries (inline or com.atproto.repo.strongRef)
Record
network.attested.payment.proof

A remote attestation record stored in the attestor’s repository (creator or broker). Referenced via com.atproto.repo.strongRef in the payment record’s signatures array.

FieldTypeDescription
cidstring (cid)Attestation CID computed per the badge.blue spec from the payment record, metadata, and repository DID
statusstringOptional status indicator for the attestation
Query (HTTP GET)
network.attested.payment.lookup

Look up verified payment records between a payer and recipient. Returns only records whose attestations have been cryptographically verified. Results may include any combination of oneTime, recurring, and scheduled payment records.

Parameters

FieldTypeDescription
payerstring (did, required)DID of the payer (supporter)
recipientstring (did, required)DID of the recipient (creator)
paymentTypestringOptional filter by payment collection. Must be a full NSID: network.attested.payment.oneTime, network.attested.payment.recurring, or network.attested.payment.scheduled
brokersarray(string) (did, optional, repeating)Optional list of broker DIDs. When provided, only returns payments that have at least one validating signature from an identity in this list
entitlementsarray(string) (at-uri, optional, repeating)Optional list of entitlement AT-URIs. When provided, only returns payments whose entitlements array contains one or more of the given references

Response

FieldTypeDescription
paymentsarray(union)List of verified payment records. Each element is a union of network.attested.payment.oneTime, network.attested.payment.recurring, or network.attested.payment.scheduled
Procedure (HTTP POST)
network.attested.payment.initiate

Begin a payment flow. Called by the payer’s client against the selected payment servicer using inter-service authentication. Returns a token for status polling and a URL to direct the payer through the payment process.

Input

FieldTypeDescription
productstring (required)Product identifier for the payment being initiated

Response

FieldTypeDescription
tokenstringOpaque token used to poll payment status via payment.status
urlstring (uri)URL to direct the payer to in a browser to complete the payment
Query (HTTP GET)
network.attested.payment.status

Check the status of a payment initiated via payment.initiate. Returns either a com.atproto.repo.strongRef pointing to the completed payment record, or a failed status.

Parameters

FieldTypeDescription
tokenstring (required)Token returned from payment.initiate

Response

FieldTypeDescription
statusstringpending | completed | failed
refcom.atproto.repo.strongRefPresent when status is completed. Points to the attested payment record in the payer’s repository

Examples

Complete record examples showing how attested payments work in practice, using badge.blue remote attestations with com.atproto.repo.strongRef entries.

One-time tip with dual attestation

A supporter sends a $25 tip to a creator. Both the creator and broker independently attest the payment by writing proof records to their own repositories.

Supporter's repo — payment record
{
  "$type": "network.attested.payment.oneTime",
  "subject": "did:plc:v5jkrb2oncmnhc7rtqhrdzwi",
  "amount": 2500,
  "currency": "USD",
  "txnid": "01J5K9P3XQHV7WNBCM2G8RFAT",
  "memo": "Great stream, keep it up!",
  "createdAt": "2025-07-14T19:22:00.000Z",
  "signatures": [
    {
      "$type": "com.atproto.repo.strongRef",
      "uri": "at://did:plc:v5jkrb2oncmnhc7rtqhrdzwi/network.attested.payment.proof/3la7qxz2vbc2s",
      "cid": "bafyreigyh7s6lqf5n3xke4jt6r2x3mqkzf4wpgicbqhqg5k3vdjn7aomfe"
    },
    {
      "$type": "com.atproto.repo.strongRef",
      "uri": "at://did:plc:broker-payments-xyz/network.attested.payment.proof/3la7qy2kbrc2t",
      "cid": "bafyreih7wwfa3tcoqd2ne5gqkvrulzpfnwjmcc5fsgqjdx4huswnhzaehqu"
    }
  ]
}
Creator's repo — proof record
{
  "$type": "network.attested.payment.proof",
  "cid": "bafyreifdw3gy6ef4mcep5forx72cilu6wbsvuajkgsxnluqemkpa5xvzrwu"
}

How the CID binds to the repo: The proof’s cid field is computed from the payment record with the supporter’s DID baked into the $sig metadata. If someone copies the payment record to a different repository, recomputing the CID produces a different value—verification fails automatically.

Monthly recurring subscription

A $10/month recurring support commitment. The record is immutable—to change the amount, the supporter cancels and creates a new subscription.

Supporter's repo — recurring payment
{
  "$type": "network.attested.payment.recurring",
  "subject": "did:plc:v5jkrb2oncmnhc7rtqhrdzwi",
  "amount": 1000,
  "currency": "USD",
  "unit": "monthly",
  "frequency": 1,
  "txnid": "01J5KBR4WMHV8XNBDM3G9SFBT",
  "createdAt": "2025-08-01T00:00:00.000Z",
  "entitlements": [
    {
      "$type": "com.atproto.repo.strongRef",
      "uri": "at://did:plc:v5jkrb2oncmnhc7rtqhrdzwi/com.example.product/3miemuswqbyiw",
      "cid": "bafyreik3xxgb5tcoqd3ne6gqkvrulzpfnwjmcc5fsgqjdx4huswnhzbekaa"
    }
  ],
  "signatures": [
    {
      "$type": "com.atproto.repo.strongRef",
      "uri": "at://did:plc:v5jkrb2oncmnhc7rtqhrdzwi/network.attested.payment.proof/3lb2rxz3vdc3t",
      "cid": "bafyreigyh7s6lqf5n3xke4jt6r2x3mqkzf4wpgicbqhqg5k3vdjn7aomfe"
    },
    {
      "$type": "com.atproto.repo.strongRef",
      "uri": "at://did:plc:broker-payments-xyz/network.attested.payment.proof/3lb2ry4ldsc3u",
      "cid": "bafyreih7wwfa3tcoqd2ne5gqkvrulzpfnwjmcc5fsgqjdx4huswnhzaehqu"
    }
  ]
}

Scheduled payment series

Six monthly payments of $50 each, terminating automatically after the final installment. Useful for project-based sponsorships or fixed commitments.

Supporter's repo — scheduled payment
{
  "$type": "network.attested.payment.scheduled",
  "subject": "did:plc:v5jkrb2oncmnhc7rtqhrdzwi",
  "amount": 5000,
  "currency": "USD",
  "unit": "monthly",
  "count": 6,
  "txnid": "01J5KCS5XRHW9YOCEN4H0TGCU",
  "createdAt": "2025-09-01T00:00:00.000Z",
  "signatures": [
    {
      "$type": "com.atproto.repo.strongRef",
      "uri": "at://did:plc:broker-payments-xyz/network.attested.payment.proof/3lc3syz4wec4u",
      "cid": "bafyreif4xxgb5tcoqd3ne6gqkvrulzpfnwjmcc5fsgqjdx4huswnhzbekru"
    }
  ]
}

Broker proof record

The broker’s independent attestation, stored in their own repository. This is the record referenced by the second strongRef in the examples above.

Broker's repo — proof record
{
  "$type": "network.attested.payment.proof",
  "cid": "bafyreih7wwfa3tcoqd2ne5gqkvrulzpfnwjmcc5fsgqjdx4huswnhzaehqu"
}

Verification flow

How an application verifies a one-time payment with dual attestation:

sequenceDiagram
    participant App as Verifying App
    participant S as Supporter's Repo
    participant C as Creator's Repo
    participant B as Broker's Repo

    App->>S: Fetch payment record
    S-->>App: payment.oneTime + signatures[]

    par Verify creator attestation
        App->>C: Fetch proof via strongRef URI
        C-->>App: payment.proof (cid)
        App->>App: Recompute CID from payment + supporter DID
        App->>App: Confirm CID matches proof.cid
    and Verify broker attestation
        App->>B: Fetch proof via strongRef URI
        B-->>App: payment.proof (cid)
        App->>App: Recompute CID from payment + supporter DID
        App->>App: Confirm CID matches proof.cid
    end

    App->>App: Payment verified ✓