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

App Developers

Integrate payment verification into your ATProtocol application.

Why Verify Payments?

Payment verification unlocks a range of features for ATProtocol applications, from supporter recognition to premium content delivery.

Gate Content

Restrict access to posts, media, or feeds based on whether a viewer has an active payment relationship with the creator.

Supporter Badges

Display visual indicators next to users who financially support a creator, building community recognition and social proof.

Premium Features

Unlock advanced functionality for paying supporters, such as extended upload limits, priority replies, or custom themes.

Payment History

Show a verifiable timeline of payments between accounts, useful for transparency dashboards and financial reporting.

Sponsorship Proof

Prove sponsorship relationships between accounts on-protocol, enabling verified sponsor listings and partnership displays.

Access Control

Combine payment status with other signals to build nuanced access policies for communities, groups, or collaborative spaces.

Checking Payment Status

The quickest way to check whether a payment exists between two accounts is to call network.attested.payment.lookup on a broker's XRPC endpoint.

Supply the payer and recipient DIDs as query parameters. You can optionally filter by paymentType using the full NSID of the payment collection (e.g. network.attested.payment.recurring). You can also pass one or more brokers DIDs to ensure results have at least one validating signature from a broker you trust, or pass entitlements AT-URIs to filter for payments that grant specific entitlements.

Request
// GET request to broker's XRPC endpoint
GET /xrpc/network.attested.payment.lookup
  ?payer=did:plc:abc123supporter
  &recipient=did:plc:xyz789creator
  &paymentType=network.attested.payment.recurring
  &brokers=did:plc:broker456
Response
{
  "payments": [
    {
      "$type": "network.attested.payment.recurring",
      "uri": "at://did:plc:abc123supporter/network.attested.payment.recurring/3abc",
      "cid": "bafyreie...",
      "recipient": "did:plc:xyz789creator",
      "amount": 500,
      "currency": "USD",
      "interval": "monthly",
      "createdAt": "2026-03-15T10:30:00Z",
      "entitlements": [
        { "uri": "at://did:plc:xyz789creator/com.example.product/3jkl", "cid": "bafyreik..." }
      ],
      "signatures": [
        { "uri": "at://did:plc:xyz789creator/network.attested.payment.proof/3def", "cid": "bafyreig..." },
        { "uri": "at://did:plc:broker456/network.attested.payment.proof/3ghi", "cid": "bafyreih..." }
      ]
    }
  ]
}

Broker discovery. It is up to the recipient to contextually hint or broadcast which payment servicers (brokers) they use. The recipient’s list may include more than one broker DID—each with a #AttestedNetwork service endpoint. Your application should resolve these DIDs and help the payer select the best option at that time, whether by preference order, availability, supported payment methods, or other criteria.

Verification Walkthrough

When you need to cryptographically verify a payment record rather than trusting a broker's lookup response, follow these five steps.

  1. Fetch the payment record from the supporter's repository using com.atproto.repo.getRecord. The record lives under the network.attested.payment collection in the payer's repo.
  2. Strip the signatures array from the record. The signatures are not part of the content-addressed payload and must be removed before hashing.
  3. Prepare attestation metadata. Take each entry from the signatures array, add the repository DID as the signer identity, and strip the cid and signature fields. Insert the resulting object as the $sig field on the record.
  4. Serialize and hash. Encode the prepared record as DAG-CBOR, hash the bytes with SHA-256, and wrap the digest as a CIDv1 with the DAG-CBOR codec (0x71).
  5. Fetch and compare proof records. Using the strongRef URIs from the signatures array, retrieve the corresponding proof records from the creator's and broker's repositories. Confirm that the CID you computed matches the CID referenced in each proof record.

Why verify locally? Broker lookup is convenient but requires trusting the broker's response. Local verification lets your app independently confirm that the payment record is authentic and has been attested by the expected parties, without relying on any single intermediary.

Trust Models

Your app can adopt different trust strategies depending on security requirements and the nature of the payment-gated feature.

Initiating Payments

Your app can trigger a payment flow on behalf of the user, guiding them through the process without leaving your interface until checkout.

  1. Resolve the recipient's DID document to discover their available payment servicers. Use the identity resolution layer of ATProtocol to fetch the DID doc.
  2. Find #AttestedNetwork service endpoints in the DID document. Each entry represents a broker or servicer that handles payments for this recipient.
  3. Present available servicers to the user. If multiple endpoints exist, let the user choose which payment provider they prefer.
  4. Call network.attested.payment.initiate on the chosen servicer's XRPC endpoint, passing the product identifier and the payer's DID.
  5. Redirect the payer to the returned URL. The servicer responds with a checkout URL and a polling token. Open the URL in the user's browser to complete the payment.
  6. Poll network.attested.payment.status with the token to track the payment outcome. On success, the response includes a strongRef pointing to the newly created payment record.
sequenceDiagram
    participant App
    participant DIDDoc as Recipient DID Doc
    participant Servicer as Payment Servicer
    participant Browser as Payer Browser

    App->>DIDDoc: Resolve recipient DID
    DIDDoc-->>App: Service endpoints (#AttestedNetwork)
    App->>Servicer: network.attested.payment.initiate
    Servicer-->>App: checkoutUrl + pollingToken
    App->>Browser: Redirect to checkoutUrl
    Browser->>Servicer: Complete payment
    Servicer-->>Browser: Payment confirmation
    loop Poll for result
        App->>Servicer: network.attested.payment.status(token)
    end
    Servicer-->>App: strongRef (success) or error (failed)
      
Initiate Request
{
  "recipient": "did:plc:xyz789creator",
  "payer": "did:plc:abc123supporter",
  "product": "monthly-subscription"
}
Initiate Response
{
  "checkoutUrl": "https://broker.example.com/pay/sess_abc123",
  "token": "poll_tok_abc123"
}
Status Response (Success)
{
  "status": "completed",
  "paymentRef": {
    "uri": "at://did:plc:abc123supporter/network.attested.payment/3abc",
    "cid": "bafyreie..."
  }
}

Private Payments

Payment records stored in Permissioned Data Spaces follow the same verification mechanics described above.

The only difference is access: records in a permissioned space are not publicly readable. Your application must hold valid space credentials to fetch the payment record and its associated proof records. Once retrieved, the verification steps (stripping signatures, computing the CID, and comparing against proof records) are identical to public payments.

Credential requirements. Contact the space operator to obtain read credentials. Your app will need to present these credentials when calling com.atproto.repo.getRecord for records stored in the permissioned space. The broker lookup endpoint may also require credentials if the broker enforces access control on private payment data.