SOPSellers' Open Protocol

Payment credentials

A payment credential is a payment authorization for one transaction: limited to a recipient, an amount and an expiry, and issued by the customer's AI agent to a seller.

LivePayment credentials for bookings are live; the first merchant, a personal trainer, turned on online payment on 2026-10-22.

What a payment credential is

A Shared Payment Token (SPT) is a Stripe product: a payment authorization that a customer's AI agent issues to a recipient, using the payment method the customer approved. The credential is valid only for this transaction, this amount, this period and this recipient.

With the credential, the recipient creates and confirms a Stripe payment (a PaymentIntent); later refunds and reporting are handled as if the customer's payment method had been provided directly.

The three roles

An SPT has three roles: the seller who is paid, the agent who issues the credential, and the platform that serves many sellers. Each is explained below with a flow diagram and how it maps to SOP.

Sellers

A seller receives the credential the agent sends and charges it. SOP handles this step for you: the payee is Seller Tech U.S.A. LLC, processed through Stripe, and funds are settled to you as agreed; fees and settlement timing are stated in the quotation.

Sellers: SOP receives and charges the payment credential for you
CustomerAgentSOPSellerApproves a paymentmethodGets a one-timecredential for SOP,limited in amount andtimeSends the credentialwith the bookingCharges the approvedamount onceBooking confirmed;settled to you asagreedProvides the service
Steps
  1. Customer → Agent: Approves a payment method
  2. Agent → Agent: Gets a one-time credential for SOP, limited in amount and time
  3. Agent → SOP: Sends the credential with the booking
  4. SOP → SOP: Charges the approved amount once
  5. SOP → Seller: Booking confirmed; settled to you as agreed
  6. Seller → Customer: Provides the service

The payment_handlers in a booking response tell the agent who the credential must be issued to, how much to pay, and when it expires:

payment_handlers in an awaiting_payment booking

JSON

[
  {
    "id": "stripe_payments",
    "type": "com.stripe.payments",
    "version": "2026-06-25",
    "spec": "https://docs.stripe.com/agentic-commerce/ucp/stripe-payments-handler",
    "schema": "https://ucp.stripe.com/payments/2026-06-25/schema.json",
    "available_instruments": [
      {
        "type": "card"
      },
      {
        "type": "link",
        "config": {
          "network_id": "profile_xxxx"
        }
      }
    ],
    "config": {
      "environment": "testmode",
      "business_profile": "profile_xxxx"
    }
  }
]

Run against a local copy of the service: Copied from the awaiting_payment booking above (booking.payment_handlers). Local copy of the connector with a placeholder profile ID; in production business_profile is the seller's real Stripe profile ID.

Agents

The agent gets the Stripe profile from the seller, collects the customer's payment method, issues an SPT through Stripe, and gives the credential to the seller. When verification is needed (for example 3D Secure), the agent walks the customer through it. Step by step, see Pay for a customer.

Agents: pay SOP with a credential the customer approved
CustomerAgentSOPrequest_bookingpayment_handlers:com.stripe.paymentsand business profile;amount; expires_atAsks to approve theexact amountApprovesIssues a SharedPayment Token forthat profile, limitedto the amount andexpirycomplete_booking withthe tokenPaid (confirmed, orpending thebusiness), orrequires_action witha 3D Secure linkConfirms, or opensthe 3D Secure link
Steps
  1. Agent → SOP: request_booking
  2. SOP → Agent: payment_handlers: com.stripe.payments and business profile; amount; expires_at
  3. Agent → Customer: Asks to approve the exact amount
  4. Customer → Agent: Approves
  5. Agent → Agent: Issues a Shared Payment Token for that profile, limited to the amount and expiry
  6. Agent → SOP: complete_booking with the token
  7. SOP → Agent: Paid (confirmed, or pending the business), or requires_action with a 3D Secure link
  8. Agent → Customer: Confirms, or opens the 3D Secure link

Pay for a booking with a Shared Payment Token

curl

# Reuse the same Idempotency-Key when you retry this request after a network error.
curl -sS -X POST "https://mcp.srct.tw/api/v1/businesses/<BUSINESS_ID>/bookings/<BOOKING_ID>/complete" \
  -H "User-Agent: my-agent/1.0" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <STATUS_TOKEN>" \
  -H "Idempotency-Key: pay-$(uuidgen)" \
  -d '{"payment":{"instruments":[{"id":"instr_1","handler_id":"stripe_payments","type":"card","selected":true,"credential":{"type":"stripe_payment_token","token":"spt_..."}}]}}'

Python

import json
import urllib.error
import urllib.request
import uuid

BASE = "https://mcp.srct.tw"


def call(method, path, body=None, headers=None):
    req = urllib.request.Request(
        BASE + path,
        data=None if body is None else json.dumps(body).encode(),
        method=method,
        headers={"User-Agent": "my-agent/1.0", "Content-Type": "application/json", **(headers or {})},
    )
    try:
        with urllib.request.urlopen(req) as res:
            return res.status, json.loads(res.read())
    except urllib.error.HTTPError as err:  # 4xx and 5xx responses still carry a JSON body
        return err.code, json.loads(err.read())


def show(status, body):
    print("HTTP", status)
    print(json.dumps(body, ensure_ascii=False, indent=2))


BUSINESS_ID = "<BUSINESS_ID>"
BOOKING_ID = "<BOOKING_ID>"      # from the create response
STATUS_TOKEN = "<STATUS_TOKEN>"  # from the create response (shown once)

payment = {
    "instruments": [{
        "id": "instr_1",
        "handler_id": "stripe_payments",  # an id from payment_handlers
        "type": "card",
        "selected": True,
        "credential": {"type": "stripe_payment_token", "token": "spt_..."},  # the SPT the agent issued
    }]
}
headers = {
    "Authorization": f"Bearer {STATUS_TOKEN}",
    "Idempotency-Key": str(uuid.uuid4()),  # reuse the same key when you retry after a network error
}

show(*call("POST", f"/api/v1/businesses/{BUSINESS_ID}/bookings/{BOOKING_ID}/complete", {"payment": payment}, headers))

Node.js

const BASE = "https://mcp.srct.tw";

async function call(method, path, body, headers = {}) {
  const res = await fetch(BASE + path, {
    method,
    headers: { "User-Agent": "my-agent/1.0", "Content-Type": "application/json", ...headers },
    body: body === undefined ? undefined : JSON.stringify(body),
  });
  return { status: res.status, body: await res.json() };
}

function show({ status, body }) {
  console.log("HTTP", status);
  console.log(JSON.stringify(body, null, 2));
}

const BUSINESS_ID = "<BUSINESS_ID>";
const BOOKING_ID = "<BOOKING_ID>"; // from the create response
const STATUS_TOKEN = "<STATUS_TOKEN>"; // from the create response (shown once)

const payment = {
  instruments: [{
    id: "instr_1",
    handler_id: "stripe_payments", // an id from payment_handlers
    type: "card",
    selected: true,
    credential: { type: "stripe_payment_token", token: "spt_..." }, // the SPT the agent issued
  }],
};
const headers = {
  Authorization: `Bearer ${STATUS_TOKEN}`,
  "Idempotency-Key": `pay-${Date.now()}`, // reuse the same key when you retry after a network error
};

(async () => {
  show(await call("POST", `/api/v1/businesses/${BUSINESS_ID}/bookings/${BOOKING_ID}/complete`, { payment }, headers));
})();

Example response

HTTP 200
{
  "ok": true,
  "booking": {
    "booking_id": "WQEMHR2E",
    "status": "pending",
    "status_text": "Waiting for the business to confirm",
    "business_id": "example-studio",
    "business_name": "Example Studio (test data)",
    "service_id": "small_group",
    "service_name": "Small-group strength class",
    "slot": "2026-10-25T10:00",
    "start": "2026-10-25T10:00:00+08:00",
    "label": "10/25(日)10:00",
    "quantity": 1,
    "quantity_unit": "seat",
    "payment": "Paid online: NT$600",
    "details": [],
    "payment_details": {
      "mode": "online",
      "status": "paid",
      "amount": {
        "value": 60000,
        "currency": "twd",
        "text": "NT$600"
      },
      "collected_by": "Seller Tech U.S.A. LLC through Stripe",
      "paid_at": "2026-10-22T11:51:00+08:00",
      "card": {
        "brand": "visa",
        "last4": "4242"
      },
      "method": "stripe_spt",
      "transaction_id": "pi_1"
    },
    "why_pending": "First booking with this business from these contact details, so the business confirms it.",
    "confirm_by": "2026-10-22T12:01:00+08:00",
    "hold": "Bookings that need the business's confirmation are held for 10 minutes. If the business does not confirm by then, the hold is released automatically and the customer can choose another slot.",
    "online_cancel_until": "2026-10-25T08:00:00+08:00"
  },
  "payment": {
    "status": "succeeded",
    "transaction_id": "pi_1",
    "instrument_type": "card"
  }
}

Response taken from the service's automated tests: Taken from the connector's test setup: the connector code runs unchanged, and Stripe is replaced by the test suite's fake Stripe server, so transaction_id (pi_1) and the processing fee (USD 1.20, shown as NT$39) are test values. Nothing was charged anywhere.

Connect platforms

A platform that serves many sellers decides who the credential is issued to: the platform's own profile, or each seller's profile. SOP currently uses the former: all merchants share the payment profile of Seller Tech U.S.A. LLC, which charges as the payee and then settles to the merchant as agreed.

Platforms: one payment profile for many businesses on SOP
CustomerAgentSOPSellerPublishes eachbusiness and thepayment profile to useApproves a paymentmethodIssues a credentialfor SOP's profileSends it with onebusiness's bookingCharges as merchantof record (SellerTech U.S.A. LLC)Confirms the bookingNotifies the booking;settles as agreedProvides the service
Steps
  1. SOP → Agent: Publishes each business and the payment profile to use
  2. Customer → Agent: Approves a payment method
  3. Agent → Agent: Issues a credential for SOP's profile
  4. Agent → SOP: Sends it with one business's booking
  5. SOP → SOP: Charges as merchant of record (Seller Tech U.S.A. LLC)
  6. SOP → Agent: Confirms the booking
  7. SOP → Seller: Notifies the booking; settles as agreed
  8. Seller → Customer: Provides the service

In the other model, each merchant has its own payment profile and is the payee itself, and SOP only collects a platform fee. That model is Planned and not implemented.

Supported regions and terms

Stripe currently lists SPT availability as the United States, Canada and some European countries; whether customers, agents and sellers in a given region can use it depends on Stripe's announcements. SPT is still a Stripe preview feature, and using SPT means accepting Stripe's related service terms.

Card data and PCI

SOP and the business never receive a full card number, expiry date or security code; Stripe handles card data. The booking record keeps the card brand and the last four digits, which the business also sees on its confirmation page.