Draft — For discussion. This specification is in active development. Join the conversation on the ATProtocol community forum.

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.

Listener's repo — recurring payment
{
  "$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.

Lookup request
// Does this listener have an active premium subscription?
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

The one-time payment record lives in the payer’s repository. In this example, a user pays $5 to get a supporter badge on a creator’s profile. The entitlement references the creator’s badge record.

Payer's repo — one-time payment
{
  "$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.

Lookup request
// Does this user have the supporter badge for this creator?
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. Different apps can define different products, all verified the same way.

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.

Nick's repo — original one-time payment
{
  "$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.

TicketsForReal's repo — updated proof record
{
  "$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.

Ted's repo — transfer payment record
{
  "$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"
    }
  ]
}

The full flow

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
{
  "$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, indicating none of the 5 credits have been consumed yet:

spellcheck.me's repo — seller proof (initial)
{
  "$type": "network.attested.payment.proof",
  "cid": "bafyreia1xxhc9udrr7of0hu0s9x7nrlzpgnxjndd8gtrkex8m7wekp1epjf",
  "uses": 0
}

The broker’s proof is a standard attestation with no usage tracking:

Broker's repo — broker proof
{
  "$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)
{
  "$type": "network.attested.payment.proof",
  "cid": "bafyreia2yyid0vesr8pg1iv1t0y8oslzqhoyknee9huskfgy9n8xflq2fqg",
  "uses": 1
}
Nick's repo — updated payment record (seller signature updated)
{
  "$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": "bafyreia2yyid0vesr8pg1iv1t0y8oslzqhoyknee9huskfgy9n8xflq2fqg"
    },
    {
      "$type": "com.atproto.repo.strongRef",
      "uri": "at://did:plc:broker-edits/network.attested.payment.proof/3lg7wz8pgvc8z",
      "cid": "bafyreih1wwfa7tcoqd6ne9gqkvrulzpfnwjmcc9fsgqjdx8huswnhzbiltu"
    }
  ]
}

Notice the seller’s signature cid changed to match the updated proof record, but the broker’s signature remains identical.

The full flow

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.