# attested.network > An open specification for decentralized, cryptographically verifiable proof-of-payments on ATProtocol. Payment proofs replace centralized payment databases with cryptographic attestation records distributed across ATProtocol repositories, so any application can independently verify a payment without relying on a single platform. This specification is a for-discussion draft in active development. Discussion is happening on the [ATProtocol community forum](https://discourse.atprotocol.community/c/wg/attested-network). --- # Specification ## Scope What this specification covers and what it intentionally leaves to implementors. ### In scope - Providing an on-protocol way of recording and validating proof-of-payment information - Supporting public and permissioned data views, processes, and relationships between buyers, sellers, and brokers - Lexicon definitions for payment record types and proof records - XRPC methods for payment initiation, status polling, and lookup - CID-based cryptographic verification via [badge.blue](https://badge.blue) attestations - Three-party attestation model (payer, recipient, broker) - Entitlements — linking payments to goods or services via `strongRef` - Trust models for application-level verification (Strict, Creator-Trusted, Federated) - Payment lifecycle: creation, cancellation, and transfer patterns - Broker role and discovery patterns - DID document `#AttestedNetwork` service endpoint schema ### Out of scope - Payment implementations — how brokers actually process transactions (Stripe, bank transfers, cash, crypto, etc.) - Entitlement record schemas — linked via `strongRef` but intentionally undefined; any lexicon can be used - Product catalogs, pricing, or tier definitions - Dispute resolution and refunds - Currency conversion - Tax reporting or compliance - Identity verification / KYC - How recipients deliver goods or services after payment This spec defines the **proof layer** — what happened and who attests to it. The **business layer** — what you're buying, how disputes work, how delivery happens — is intentionally left to participants and their chosen brokers. ## 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. Record distribution: - **Supporter's repo**: Payment record (`oneTime` | `recurring` | `scheduled`) containing a `signatures` array of strongRefs - **Creator's repo**: `payment.proof` attesting via CID - **Broker's repo**: `payment.proof` attesting via CID The `signatures` array in the payment record contains `strongRef` entries pointing to each proof record. ### Core properties - **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](https://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](https://dholms.leaflet.pub/3mhj6bcqats2o) 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. ## 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. Because CID computation strips the `signatures` field, seller or broker proofs can be updated independently without invalidating the other party's attestation. This enables patterns like consumable entitlements (where a seller increments a `uses` counter) without breaking broker signatures. ## Trust Models Applications can implement different validation strategies: - **Strict**: Require proofs from both the creator and a specific trusted broker. Highest assurance. - **Creator-Trusted**: Accept any payment the creator has attested. Simpler, trusts creators to vouch for supporters. - **Federated**: Accept attestations from a set of trusted brokers. Enables regional verification networks. ## 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. | Field | Type | Description | |-------|------|-------------| | `subject` | string (did) | DID of the creator receiving payment | | `amount` | integer | Payment amount in smallest currency unit (e.g. cents). Min: 1 | | `currency` | string | ISO 4217 currency code (e.g. `USD`, `EUR`) | | `txnid` | string | Unique transaction identifier for deduplication | | `memo` | string | Optional note from the supporter. Max 256 chars | | `createdAt` | string (datetime) | Timestamp of record creation | | `entitlements` | array(`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 | | `signatures` | array | Attestation 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. | Field | Type | Description | |-------|------|-------------| | `subject` | string (did) | DID of the creator receiving payment | | `amount` | integer | Amount per billing period in smallest currency unit. Min: 500, Max: 25000 (monthly equivalent) | | `currency` | string | ISO 4217 currency code | | `unit` | string | Billing period: `monthly` \| `quarterly` \| `semiannual` \| `yearly` | | `frequency` | integer | Billing frequency multiplier. Default: 1, Min: 1 | | `txnid` | string | Unique transaction identifier for deduplication | | `createdAt` | string (datetime) | Timestamp of initial subscription creation | | `entitlements` | array(`com.atproto.repo.strongRef`) | Optional list of strong references to records representing any goods or services the payer is entitled to | | `signatures` | array | Attestation 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. | Field | Type | Description | |-------|------|-------------| | `subject` | string (did) | DID of the creator receiving payment | | `amount` | integer | Amount per payment in smallest currency unit | | `currency` | string | ISO 4217 currency code | | `unit` | string | Interval: `monthly` \| `quarterly` \| `semiannual` \| `yearly` | | `count` | integer | Total number of scheduled payments. Min: 2, Max: 60 | | `txnid` | string | Unique transaction identifier for deduplication | | `createdAt` | string (datetime) | Timestamp of schedule creation; first payment date | | `entitlements` | array(`com.atproto.repo.strongRef`) | Optional list of strong references to records representing any goods or services the payer is entitled to | | `signatures` | array | Attestation 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. | Field | Type | Description | |-------|------|-------------| | `cid` | string (cid) | Attestation CID computed per the [badge.blue](https://badge.blue) spec from the payment record, metadata, and repository DID | | `status` | string | Optional status indicator for the attestation (e.g. `transferred`) | ### Query: `network.attested.payment.lookup` (HTTP GET) 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:** | Field | Type | Description | |-------|------|-------------| | `payer` | string (did, required) | DID of the payer (supporter) | | `recipient` | string (did, required) | DID of the recipient (creator) | | `paymentType` | string | Optional filter by payment collection. Must be a full NSID: `network.attested.payment.oneTime`, `network.attested.payment.recurring`, or `network.attested.payment.scheduled` | | `brokers` | array(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 | | `entitlements` | array(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:** | Field | Type | Description | |-------|------|-------------| | `payments` | array(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: `network.attested.payment.initiate` (HTTP POST) 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:** | Field | Type | Description | |-------|------|-------------| | `product` | string (required) | Product identifier for the payment being initiated | **Response:** | Field | Type | Description | |-------|------|-------------| | `token` | string | Opaque token used to poll payment status via `payment.status` | | `url` | string (uri) | URL to direct the payer to in a browser to complete the payment | ### Query: `network.attested.payment.status` (HTTP GET) 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:** | Field | Type | Description | |-------|------|-------------| | `token` | string (required) | Token returned from `payment.initiate` | **Response:** | Field | Type | Description | |-------|------|-------------| | `status` | string | `pending` \| `completed` \| `failed` | | `ref` | com.atproto.repo.strongRef | Present when status is `completed`. Points to the attested payment record in the payer's repository | ## Examples ### 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:** ```json { "$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:** ```json { "$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:** ```json { "$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:** ```json { "$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: ```json { "$type": "network.attested.payment.proof", "cid": "bafyreih7wwfa3tcoqd2ne5gqkvrulzpfnwjmcc5fsgqjdx4huswnhzaehqu" } ``` --- # Payment Brokers How to implement a payment servicer that processes transactions and writes attestation proofs. ## What is a Broker? A broker (or payment servicer) facilitates payment between payers and recipients on ATProtocol and acts as a witness to the exchange. A broker is whatever facilitates the exchange of value. One broker might process Stripe transactions. Another might handle peer-to-peer cash. Another might simply witness a virtual high-five. The role is to **facilitate and witness** the exchange between payer and recipient, then write `network.attested.payment.proof` records and expose XRPC endpoints for payment initiation and verification. Recipients publish an ordered list of broker DIDs with `#AttestedNetwork` service endpoints in their DID document. When a payer wants to pay a recipient, the app resolves the recipient's DID document, finds the broker endpoints, and initiates the payment flow through one of them. **Key principle:** Brokers are untrusted intermediaries. The attestation proof they write is independently verifiable by anyone — the CID-based proof chain means you can confirm a payment without trusting the broker's word. ### Responsibilities - Accept payment initiation requests via XRPC from apps acting on behalf of payers - Facilitate the actual exchange (Stripe transaction, peer-to-peer cash, or any other mechanism) - Write payment records to the payer's repository via inter-service auth - Compute attestation CIDs and write proof records to the broker's own repository - Expose status and lookup endpoints so apps can verify payment state ## DID Document Setup Brokers must configure an `#AttestedNetwork` service endpoint in their DID document so recipients can discover and reference them. When a recipient adds your broker to their supported servicers list, they reference your DID. Apps then resolve your DID document, find the `#AttestedNetwork` service entry, and use its `serviceEndpoint` as the base URL for all XRPC calls. Add the following service entry to your DID document: ```json { "id": "#AttestedNetwork", "type": "AttestedNetworkServicer", "serviceEndpoint": "https://pay.example.com" } ``` The `serviceEndpoint` URL must be HTTPS and should point to the root of your XRPC service. All three payment methods (`initiate`, `status`, `lookup`) will be called against this base URL. ## XRPC Methods to Implement Brokers must implement `network.attested.payment.initiate`, `network.attested.payment.status`, and `network.attested.payment.lookup` (see the Lexicon section above for full schemas). **Auth model:** The `initiate` method uses ATProtocol inter-service auth — the calling app presents a signed service token on behalf of the payer. The `status` and `lookup` methods can be called with standard auth or unauthenticated, depending on your privacy requirements. ## Writing Attestation Proofs When a payment completes, the broker writes the payment record and a corresponding attestation proof. This is the core of the system. ### Steps 1. Write the payment record (`oneTime`, `recurring`, or `scheduled`) to the **payer's repository** using inter-service auth. 2. Compute the attestation CID per the badge.blue spec: strip signatures from the record, add a `$sig` field containing the payer's repository DID, DAG-CBOR serialize, SHA-256 hash, produce a CIDv1. 3. Write a `network.attested.payment.proof` record to the **broker's own repository** containing the CID and an optional `status`. 4. Update the payment record's `signatures` array in the payer's repo with a `com.atproto.repo.strongRef` pointing to the proof record. **Proof record example:** ```json { "$type": "network.attested.payment.proof", "cid": "bafyreig5..." } ``` ## Private Payments For payments that should not be publicly visible, brokers manage Permissioned Data Spaces on behalf of the payer. When a payment is marked as private, the broker additionally creates a permissioned space in the payer's repository and ensures that only the relevant parties — the payer, the recipient, and the broker itself — have read access to the payment record. The attestation mechanics are identical to public payments. The proof record is still written to the broker's repository and the CID chain is still independently verifiable. The only difference is that the underlying payment record lives inside a permissioned space rather than being publicly readable. **Implementation note:** The broker must have write access to the payer's permissioned data space collection. This is granted via the same inter-service auth mechanism used for writing payment records. Ensure your access control logic correctly scopes permissions to only the records your broker manages. ## Invalidation When support ends — whether through cancellation, failed retries, or refund — the broker should invalidate the attestation proof. **Guidance, not requirements.** The following invalidation and retry processes are suggested patterns, not enforced rules. Brokers are free to implement whatever process they believe to be appropriate for their use case and their users. To invalidate a payment, the broker deletes or updates its `network.attested.payment.proof` record and notifies the recipient. Apps that verify payments will see the proof is missing or marked invalid and treat the payment as no longer active. ### Recurring Payment Retries For recurring payments, a failed charge does not immediately invalidate the attestation. The broker should follow this retry schedule: - **Day 1:** First retry attempt after the initial charge failure. - **Day 3:** Second retry attempt. - **Day 7:** Third and final retry attempt. - **Day 8 (grace period expires):** If all retries have failed, the broker deletes the proof record and notifies the recipient that the payment is no longer active. **Grace period:** During the 8-day retry window, the existing proof record remains valid. The payer's access should not be interrupted while retries are in progress. Only after the grace period expires and all retries have failed should the broker invalidate the proof. --- # 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 /xrpc/network.attested.payment.lookup ?payer=did:plc:abc123supporter &recipient=did:plc:xyz789creator &paymentType=network.attested.payment.recurring &brokers=did:plc:broker456 ``` **Response:** ```json { "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. - **Strict**: Require attestation proofs from both the creator (recipient) and a specific trusted broker. This is the most secure model and is recommended for high-value access control, financial dashboards, or any context where payment fraud would have significant consequences. - **Creator-Trusted**: Accept any payment record that the creator has attested, regardless of which broker facilitated it. Suitable for social features like supporter badges or shout-outs, where the creator's endorsement is the primary signal. - **Federated**: Maintain a set of trusted broker DIDs and accept attestations from any of them. This balances security with flexibility, allowing your app to work with multiple brokers while still filtering out unknown or untrusted attestors. Good for platforms that aggregate content from many creators. ## 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. **Initiate request:** ```json { "recipient": "did:plc:xyz789creator", "payer": "did:plc:abc123supporter", "product": "monthly-subscription" } ``` **Initiate response:** ```json { "checkoutUrl": "https://broker.example.com/pay/sess_abc123", "token": "poll_tok_abc123" } ``` **Status response (success):** ```json { "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. --- # Recipients Set up your identity to accept attested payments from supporters. ## How It Works When someone supports you through an attested payment, three things happen simultaneously — creating a verifiable record that no single party controls. A **payment record** is written to your supporter's repository, declaring that they made a payment. A **proof record** is written to your repository (by the broker acting on your behalf), confirming you received it. And the **broker writes their own proof** to their repository, witnessing the exchange. This three-way structure is what makes attested payments powerful. Any application on ATProtocol can look at these three independent records and verify the support relationship — without asking permission from any platform or payment processor. **Why three records?** If only your supporter had a record, they could fabricate payments. If only you had a record, you could fabricate supporters. The broker's independent proof ties it all together — and because all three are on ATProtocol, any app can verify the relationship without trusting any single party. ## Choosing Payment Servicers You signal which payment servicers (brokers) you use by publishing an ordered list in your DID document. Brokers handle the actual payment processing — credit cards, bank transfers, and other payment methods — so you don't have to. You publish an ordered list of broker DIDs, each referencing their `#AttestedNetwork` service endpoint. The order matters: it represents your preference ranking, so apps know which servicer to try first. You can use **multiple brokers simultaneously**. This gives your supporters options and protects you from relying on a single provider. If one broker goes down or raises fees, your other servicers continue working immediately. The ecosystem is designed for competition and choice. Brokers can be regional (focused on local payment methods), non-profit (community-run cooperatives), crypto-native (accepting digital currencies), or traditional payment processors. You pick the ones that work for you and your audience. Your choice of brokers also affects discoverability. Apps can query for payments that have been validated by specific brokers — so using well-known, trusted servicers means your payment proofs are more likely to be recognized and accepted across the ecosystem. **No lock-in.** Switching brokers is as simple as updating the list in your DID document. Your existing payment history and proofs remain valid regardless of which servicers you use going forward. ## Setting Up Getting started takes three steps. Your chosen servicer handles everything else. 1. **Choose one or more payment servicers (brokers).** Browse available brokers and pick the ones that support the payment methods your audience uses. You can start with one and add more later. 2. **Add their DIDs to your DID document.** Publish an ordered list of broker DIDs, each referencing their `#AttestedNetwork` service endpoint. The order signals your preference — apps will try brokers in the order you list them. 3. **Configure your products or tiers with each servicer.** This happens on the servicer's platform. Set up your pricing, subscription tiers, or tip amounts. The servicer will guide you through their onboarding process. That's it. Once your DID document lists your servicers and you've configured your offerings, supporters can start paying you through any ATProtocol app that supports attested payments. ## What Happens When Someone Pays When a supporter pays you, their app resolves your DID document, finds your servicer list, and initiates the payment through your preferred broker. The broker processes the payment, writes the payment record to the supporter's repository, and writes proof records to your repository and the broker's repository. The key thing to notice is that **you don't have to do anything** once you're set up. The servicer handles the payment, writes the records, and everything is automatically verifiable. You don't need to be online, run a server, or respond to webhooks. ## Payment Types Attested payments support three types, each suited to different creator needs. - **One-time**: Single tips, donations, or one-off purchases. A supporter pays once and receives a permanent, verifiable proof of that payment. Great for tipping on individual posts or making a one-time contribution. - **Recurring**: Ongoing subscriptions that renew automatically — monthly, quarterly, or at any interval you choose. The servicer handles renewals and writes fresh proofs each period. Ideal for ongoing creator support or membership access. - **Scheduled**: A fixed series of payments with a defined end. For example, six monthly payments to sponsor a project, or four quarterly payments for a course. The total commitment is clear upfront for both you and your supporter. ## Portability Your supporter relationships belong to you and your supporters — not to any platform. Every payment record and proof lives on ATProtocol, not in a proprietary database. This means your entire payment history — who supports you, when they started, what they've contributed — travels with you across the network. If you **switch apps**, your new app can read the same records and display your supporters immediately. If you **change servicers**, your existing payment history remains intact and verifiable. Active subscriptions continue to be honored because the proof records are independent of which servicer processed them. This is a fundamental departure from traditional platforms where your subscriber list, payment history, and supporter relationships are locked inside a single service. On ATProtocol, those relationships are yours. **Built on open infrastructure.** Attested payments use [badge.blue](https://badge.blue) attestations and [ATProtocol](https://atproto.com) repositories. Every record is content-addressed and cryptographically bound to its repository — portable, verifiable, and owned by the people who created them. --- # Payers How your payments are recorded, verified, and portable across ATProtocol. ## How Your Payments Work A simple system with strong guarantees. When you pay to support a creator, a payment record is written to your ATProtocol repository. This isn't just a receipt — it's a cryptographic proof that independent parties can verify. The creator and a payment servicer (called a broker) both write their own confirmation records. The broker facilitates and witnesses the exchange — whether that's a credit card transaction, a cash payment, or any other mechanism. Together, these three records form an unbreakable chain of proof. Because all three records are independently signed and stored, no single party can falsify or revoke your payment history. Your proof stands on its own. ## The Payment Experience What you actually see when you make a payment: 1. You find a creator you want to support. 2. Your app shows you the available payment methods — these are the creator's preferred servicers. 3. You choose a servicer and select your payment type: a one-time tip, a recurring subscription, or a scheduled series of payments. 4. You're directed to a payment page where you enter your details and confirm. 5. Once complete, the proof appears in your account — a verified record you own forever. ## Payment Types Three ways to support the people you care about: - **One-time**: A single tip or donation in any amount. Perfect for saying thanks, supporting a specific piece of work, or trying out a creator before committing to ongoing support. - **Recurring**: An ongoing subscription that renews automatically. Choose monthly, quarterly, semi-annual, or yearly. Recurring payments are immutable — to change the amount, cancel the current one and start fresh. - **Scheduled**: A fixed number of payments spread over time — for example, 6 monthly payments. The series ends automatically when all payments are complete. No need to remember to cancel. ## Your Data, Your Records You own your payment history. Always. Payment records live in your repository, not in a company's database. You own them. They're signed with your identity and stored under your control. If you switch apps or platforms, your payment history comes with you. No export needed, no data request forms, no waiting. Your records are already yours. No platform can lock you out of your own payment records. Even if a service shuts down, your proof of payment remains intact and verifiable. ## Privacy Options You decide who sees your payment activity. **Public payments** are visible in your repository for anyone to verify. This is great for transparency — creators can showcase their supporters, and you can show your support publicly. **Private payments** are stored in a Permissioned Data Space where only authorized parties can see them. The creator and broker can still verify the payment, but it won't appear in your public repository. Both options use the same verification mechanics. Private payments aren't less trustworthy — they're just less visible. Choose whichever feels right for you. ## Cancellation & Changes Simple, predictable rules for managing your payments. When you cancel a recurring or scheduled payment, the cancellation is immediate and intentional. There's no ambiguity — a cancellation record is written to confirm it. Active recurring payments complete the current period before ending. If you've already paid for this month, you keep your access through the end of that period. Payment records are immutable — you can't edit one after it's created. If you want different terms, cancel the existing payment and start a new one. This keeps your payment history clean and auditable. Pre-paid support (like a scheduled series you've already funded) continues through the funded period even after cancellation. You've already paid for it, so you keep it. --- # Example Scenarios Real-world usage patterns showing how attested payments work end-to-end. ## Podcast Subscription A podcast offers a $10/month premium subscription. Supporters pay through a broker, which creates a recurring payment record with an entitlement that grants access to premium content. Any app, tile, or appview can verify the subscription by checking for an active payment with the matching entitlement. ### How it works The podcast creator publishes their content on ATProtocol and uses a broker to handle subscriptions. When a listener subscribes, three things happen: 1. The listener's client initiates a payment through the broker via `network.attested.payment.initiate`. 2. The broker processes the $10/month payment and writes a `network.attested.payment.recurring` record to the listener's repository, including an `entitlements` reference pointing to the podcast's product record. 3. The broker and creator each write `payment.proof` attestation records to their own repositories, linked via the payment's `signatures` array. From that point on, any app can verify the subscription by calling `network.attested.payment.lookup` with the entitlement AT-URI. No centralized subscription database needed. ### The payment record The recurring payment record lives in the listener's repository. It declares the $10/month commitment, references the podcast's product record as an entitlement, and carries attestation signatures from both the creator and broker. ```json { "$type": "network.attested.payment.recurring", "subject": "did:plc:podcast-creator", "amount": 1000, "currency": "USD", "unit": "monthly", "frequency": 1, "txnid": "01J6M4R5XQHV8WNBCM3G9RFBT", "createdAt": "2026-03-01T00:00:00.000Z", "entitlements": [ { "$type": "com.atproto.repo.strongRef", "uri": "at://did:plc:podcast-creator/com.example.podcast.subscription/premium", "cid": "bafyreig7xxgb5tcoqd3ne6gqkvrulzpfnwjmcc5fsgqjdx4huswnhzbekcc" } ], "signatures": [ { "$type": "com.atproto.repo.strongRef", "uri": "at://did:plc:podcast-creator/network.attested.payment.proof/3la8rxz3vdc4t", "cid": "bafyreigyh7s6lqf5n3xke4jt6r2x3mqkzf4wpgicbqhqg5k3vdjn7aomfe" }, { "$type": "com.atproto.repo.strongRef", "uri": "at://did:plc:broker-payments/network.attested.payment.proof/3la8ry4ldsc4u", "cid": "bafyreih7wwfa3tcoqd2ne5gqkvrulzpfnwjmcc5fsgqjdx4huswnhzaehqu" } ] } ``` ### Verifying access When a listener opens the podcast in any ATProtocol app, the app checks for an active subscription by querying the broker's lookup endpoint. The key is filtering by both the recurring payment type and the specific entitlement. ``` GET /xrpc/network.attested.payment.lookup ?payer=did:plc:listener123 &recipient=did:plc:podcast-creator &paymentType=network.attested.payment.recurring &entitlements=at://did:plc:podcast-creator/com.example.podcast.subscription/premium ``` If the response contains a payment record, the listener has an active subscription and the app grants access to premium episodes. If the `payments` array is empty, the listener either hasn't subscribed or their subscription has lapsed. **App-agnostic verification.** Because the payment record and its entitlements live on the protocol, any podcast app, tile, or appview can independently verify the subscription. The listener isn't locked into a single client — their subscription follows them across the ecosystem. **Entitlements are flexible.** The `com.example.podcast.subscription/premium` record is defined by the podcast creator, not by this spec. It could contain tier details, feature flags, or access rules — whatever makes sense for the product. The payment record simply references it as a `strongRef`, linking proof of payment to what was purchased. ## One-Time Purchase A user makes a one-time payment to unlock something tied to a specific recipient — a profile badge in a social app, a tip that comes with an award, downloadable content, or any other digital good. The payment is linked to an entitlement record that the app checks to gate access or display. ### How it works A user wants to support a creator and get something in return — maybe a supporter badge that appears on their profile, or access to bonus content. The app presents the option, the user pays through a broker, and the resulting payment record carries an entitlement that any app can verify. 1. The user selects a purchase option in the app (e.g. "Buy Supporter Badge" or "Unlock Bonus Pack"). The app initiates the payment via `network.attested.payment.initiate` on the creator's broker. 2. The broker processes the one-time payment and writes a `network.attested.payment.oneTime` record to the payer's repository. The record includes an `entitlements` reference pointing to the specific product — a badge, an award, DLC content, or whatever the creator has defined. 3. The broker and creator each write `payment.proof` attestation records to their own repositories, completing the cryptographic chain. When any app wants to check whether the user has purchased that item, it calls `network.attested.payment.lookup` filtered by the entitlement AT-URI. If a matching one-time payment exists, the user has paid and the app renders the badge, unlocks the content, or grants whatever the entitlement represents. ### The payment record A user pays $5 to get a supporter badge on a creator's profile. The entitlement references the creator's badge record. ```json { "$type": "network.attested.payment.oneTime", "subject": "did:plc:creator-xyz", "amount": 500, "currency": "USD", "txnid": "01J7N5S6YRHW0XPBDN4H1UHEV", "memo": "Supporter badge", "createdAt": "2026-03-20T14:30:00.000Z", "entitlements": [ { "$type": "com.atproto.repo.strongRef", "uri": "at://did:plc:creator-xyz/com.example.app.badge/supporter", "cid": "bafyreif8xxgb6tcoqd4ne7gqkvrulzpfnwjmcc6fsgqjdx5huswnhzbekdd" } ], "signatures": [ { "$type": "com.atproto.repo.strongRef", "uri": "at://did:plc:creator-xyz/network.attested.payment.proof/3ld4txz5xfe5v", "cid": "bafyreigyh8s7lqf6n4xke5jt7r3x4mqkzf5wpgicbqhqg6k4vdjn8bomge" }, { "$type": "com.atproto.repo.strongRef", "uri": "at://did:plc:broker-payments/network.attested.payment.proof/3ld4ty5mdtc5w", "cid": "bafyreih8wwfa4tcoqd3ne6gqkvrulzpfnwjmcc6fsgqjdx5huswnhzafiqu" } ] } ``` ### Verifying the purchase When an app renders a user's profile, it checks whether the user has purchased the supporter badge for that creator by querying the lookup endpoint with the entitlement AT-URI. ``` GET /xrpc/network.attested.payment.lookup ?payer=did:plc:user456 &recipient=did:plc:creator-xyz &paymentType=network.attested.payment.oneTime &entitlements=at://did:plc:creator-xyz/com.example.app.badge/supporter ``` If the `payments` array contains a matching record, the app displays the badge on the user's profile. Because this is a one-time payment, it persists indefinitely — there's no renewal to check. **Many forms, same pattern.** This scenario covers any one-time digital purchase: supporter badges, tip awards, DLC content packs, custom emoji sets, premium filters, or exclusive stickers. The entitlement record is defined by the app or creator — the payment spec just links proof of payment to whatever was purchased. ## Entitlement Transfer A user purchases a concert ticket, can't make it, and transfers the entitlement to someone else through the broker. The original payment is marked as transferred, and a new payment record is created in the recipient's repository referencing the transfer transaction. ### Step 1: The original purchase Nick buys a ticket to TheGreatestBand's concert on May 1st through TicketsForReal. This is a standard one-time payment with the concert ticket as an entitlement. ```json { "$type": "network.attested.payment.oneTime", "subject": "did:plc:thegreatestband", "amount": 7500, "currency": "USD", "txnid": "01J8P6T7ZSHX1YQCEO5I2VIFV", "memo": "Concert ticket - May 1st 2026", "createdAt": "2026-03-10T09:00:00.000Z", "entitlements": [ { "$type": "com.atproto.repo.strongRef", "uri": "at://did:plc:thegreatestband/com.example.tickets.event/may1-2026", "cid": "bafyreig9xxgb7tcoqd5ne8gqkvrulzpfnwjmcc7fsgqjdx6huswnhzbekee" } ], "signatures": [ { "$type": "com.atproto.repo.strongRef", "uri": "at://did:plc:thegreatestband/network.attested.payment.proof/3le5uxz6yge6w", "cid": "bafyreigyh9s8lqf7n5xke6jt8r4x5mqkzf6wpgicbqhqg7k5vdjn9comhe" }, { "$type": "com.atproto.repo.strongRef", "uri": "at://did:plc:ticketsforreal/network.attested.payment.proof/3le5uy6neuc6x", "cid": "bafyreih9wwfa5tcoqd4ne7gqkvrulzpfnwjmcc7fsgqjdx6huswnhzbgjru" } ] } ``` At this point, Nick's repository has the payment record, TheGreatestBand's repository has a proof record, and TicketsForReal's repository has a proof record. Standard one-time purchase flow. ### Step 2: Initiating the transfer Nick can't make the concert and decides to transfer the ticket to Ted. He initiates the transfer through the TicketsForReal website. The broker updates the `status` field on its proof record to mark the original payment as transferred. ```json { "$type": "network.attested.payment.proof", "cid": "bafyreih9wwfa5tcoqd4ne7gqkvrulzpfnwjmcc7fsgqjdx6huswnhzbgjru", "status": "transferred" } ``` Any app that verifies Nick's ticket will now see the broker's proof status is `transferred`, indicating the entitlement is no longer valid for Nick. The original payment record in Nick's repository remains untouched — it still serves as a receipt proving the purchase happened. ### Step 3: Claiming the transfer Ted logs in to the TicketsForReal website to claim the transferred ticket. The broker creates a new `payment.oneTime` record in Ted's repository. This record references the same concert entitlement but with a new transaction ID for the transfer, and carries its own proof records from both TheGreatestBand and TicketsForReal. ```json { "$type": "network.attested.payment.oneTime", "subject": "did:plc:thegreatestband", "amount": 0, "currency": "USD", "txnid": "01J9Q7U8ATHX2ZRDFO6J3WJGW", "memo": "Transfer from did:plc:nick", "createdAt": "2026-04-15T16:45:00.000Z", "entitlements": [ { "$type": "com.atproto.repo.strongRef", "uri": "at://did:plc:thegreatestband/com.example.tickets.event/may1-2026", "cid": "bafyreig9xxgb7tcoqd5ne8gqkvrulzpfnwjmcc7fsgqjdx6huswnhzbekee" } ], "signatures": [ { "$type": "com.atproto.repo.strongRef", "uri": "at://did:plc:thegreatestband/network.attested.payment.proof/3lf6vyz7zhe7x", "cid": "bafyreigyh0s9lqf8n6xke7jt9r5x6mqkzf7wpgicbqhqg8k6vdjn0domie" }, { "$type": "com.atproto.repo.strongRef", "uri": "at://did:plc:ticketsforreal/network.attested.payment.proof/3lf6vz7ofvc7y", "cid": "bafyreih0wwfa6tcoqd5ne8gqkvrulzpfnwjmcc8fsgqjdx7huswnhzbhksu" } ] } ``` **Transfer verification.** When an app checks who holds the ticket, it queries `network.attested.payment.lookup` with the concert entitlement AT-URI. Nick's original payment will no longer match — the broker's proof has a `transferred` status. Ted's payment record, with fresh proofs from both TheGreatestBand and TicketsForReal, is the current valid holder. **Broker-managed process.** Transfers are facilitated entirely by the broker. The broker controls the transfer policy — whether transfers are allowed, whether fees apply, how many times a ticket can change hands. The spec provides the primitives (proof status, new payment records) but the business logic is up to the broker. ## Consumable Entitlements A "punch card" model where a one-time purchase grants a fixed number of uses. Each use is tracked by the seller updating their proof record, while the broker's attestation remains unchanged throughout. ### Step 1: Purchasing the punch card Nick visits spellcheck.me and buys a punch card for 5 uses of their editing service for $20. This is a standard one-time purchase, but the seller's proof record includes a `uses` field initialized to `0`. **Nick's repo — one-time payment:** ```json { "$type": "network.attested.payment.oneTime", "subject": "did:plc:spellcheckme", "amount": 2000, "currency": "USD", "txnid": "01JA0R8V9BIY3ASEGP7K4XKHX", "memo": "Punch card - 5 edits", "createdAt": "2026-04-01T10:00:00.000Z", "entitlements": [ { "$type": "com.atproto.repo.strongRef", "uri": "at://did:plc:spellcheckme/com.example.spellcheck.punchcard/5pack", "cid": "bafyreig0xxgb8tcoqd6ne9gqkvrulzpfnwjmcc8fsgqjdx7huswnhzbekff" } ], "signatures": [ { "$type": "com.atproto.repo.strongRef", "uri": "at://did:plc:spellcheckme/network.attested.payment.proof/3lg7wyz8aif8y", "cid": "bafyreia1xxhc9udrr7of0hu0s9x7nrlzpgnxjndd8gtrkex8m7wekp1epjf" }, { "$type": "com.atproto.repo.strongRef", "uri": "at://did:plc:broker-edits/network.attested.payment.proof/3lg7wz8pgvc8z", "cid": "bafyreih1wwfa7tcoqd6ne9gqkvrulzpfnwjmcc9fsgqjdx8huswnhzbiltu" } ] } ``` The seller's proof record starts with a `uses` counter at `0`: ```json { "$type": "network.attested.payment.proof", "cid": "bafyreia1xxhc9udrr7of0hu0s9x7nrlzpgnxjndd8gtrkex8m7wekp1epjf", "uses": 0 } ``` The broker's proof is a standard attestation with no usage tracking: ```json { "$type": "network.attested.payment.proof", "cid": "bafyreih1wwfa7tcoqd6ne9gqkvrulzpfnwjmcc9fsgqjdx8huswnhzbiltu" } ``` ### Step 2: Using a credit Nick writes a Leaflet post and wants it reviewed, so he visits spellcheck.me and submits his article. When the service processes the request, two things happen atomically: 1. **The seller updates their proof record.** The `uses` field increments from `0` to `1`. Because the proof record's content changed, its `cid` is recomputed to reflect the new data. 2. **The payment record in Nick's PDS updates.** The `signatures` array's `strongRef` for the seller's proof is updated to point to the new CID, keeping the reference in sync with the changed proof. **spellcheck.me's repo — seller proof (after 1 use):** ```json { "$type": "network.attested.payment.proof", "cid": "bafyreia2yyid0vesr8pg1iv1t0y8oslzqhoyknee9huskfgy9n8xflq2fqg", "uses": 1 } ``` The payment record's seller signature `cid` changes to match the updated proof record, but the broker's signature remains identical. **Why the broker proof is unaffected.** The attestation CID in the broker's proof is computed from the payment record *without* the `signatures` field. When the seller updates their proof and the payment record's `signatures` array changes to point to the new seller CID, the broker's attestation CID remains valid — it was never based on the signatures in the first place. This is what makes metered usage possible without re-attesting with the broker on every use. **Seller-managed consumption.** The seller controls the usage counter in their own proof record. When `uses` reaches the purchased limit (5 in this case), the seller simply stops accepting new requests. The spec doesn't enforce the limit — the seller's application logic does. Any app can read the seller's proof to see how many credits remain. --- # References - [badge.blue](https://badge.blue): The underlying CID-first attestation framework this spec builds on. - [ATProtocol](https://atproto.com): The protocol this specification is written for. - [Discussion forum](https://discourse.atprotocol.community/c/wg/attested-network): Working group thread on the ATProtocol community forum. - [Permissioned Data Spaces](https://dholms.leaflet.pub/3mhj6bcqats2o): The mechanism used for private payment records.