# MPP payments

Use MPP for machine-to-machine payments.

Find the app’s [complete source code](https://github.com/stripe-samples/machine-payments) on GitHub.

[MPP, the Machine Payments Protocol](https://mpp.dev), is a protocol for internet payments. When a client requests a paid resource, your server returns an HTTP `402` response with payment details. The client authorizes the payment, retries the request, pays, and gets access to the paid resource along with a receipt.

MPP supports two payment methods:

- **Fiat payments**: Card, wallet, and other payment methods that [shared payment tokens (SPTs)](https://docs.stripe.com/agentic-commerce/concepts/shared-payment-tokens.md) support. Available to businesses with a US or Canada legal entity.
- **Crypto payments**: Direct on-chain payment that uses crypto deposit addresses. Available to businesses with physical locations in all states, except New York, and in more than 30 countries.

## Before you begin

> Stablecoin payments are available to businesses in all US states, except New York. For businesses operating outside of the US, email [machine-payments@stripe.com](mailto:machine-payments@stripe.com) with your Stripe account ID to request access to stablecoin payments in more than 30 countries.

To start accepting stablecoin payments:

1. Make sure you’ve [set up your Stripe account](https://dashboard.stripe.com/register).
2. Go to your [Payment methods](https://dashboard.stripe.com/settings/payment_methods) settings in the Dashboard and request the **Stablecoins and Crypto** payment method. If you want to accept stablecoin or crypto payments only for [machine payments](https://docs.stripe.com/payments/machine.md), create a separate [payment method configuration](https://docs.stripe.com/payments/payment-method-configurations.md) dedicated to machine payments.
3. Stripe reviews your access request and contacts you for more details if necessary. The payment method appears as **Pending** while we review your request.
4. After we approve your request, the **Stablecoins and Crypto** payment method becomes active in the Dashboard.

To accept fiat payments with SPTs:

1. [Create a Stripe profile](https://docs.stripe.com/get-started/account/profile.md) in the Stripe Dashboard.
2. Store your profile’s `profile_` ID. You use this value as the `networkId` in the following SPT configuration. A profile ID identifies a business on the Stripe network and is required for your server integration.

## Payment lifecycle

In this guide, you build the server. Your server indicates that payment is required and returns the content after successful payment.

#### Crypto
A diagram showing the MPP crypto payment flow between client, server, and Stripe (See full diagram at https://docs.stripe.com/payments/machine/mpp)
You create a deposit address once, and record each on-chain payment as a [PaymentIntent](https://docs.stripe.com/api/payment_intents.md) using `transaction_verification` mode after funds settle on-chain.

#### SPT
A diagram showing the MPP SPT payment flow between client, server, and Stripe (See full diagram at https://docs.stripe.com/payments/machine/mpp)
With [shared payment token payments (SPTs)](https://docs.stripe.com/agentic-commerce/concepts/shared-payment-tokens.md), the client creates an SPT, and the server creates a `PaymentIntent` with the token. Settlement completes through Stripe’s payment rails.

## Use a coding agent

You can build an API that uses MPP with a single prompt to your coding agent:

```bash
Read https://docs.stripe.com/payments/machine/mpp.md?lang=node, and monetize my API using MPP to charge for access using Stripe SPTs for fiat and the Tempo network for crypto. Run `npx mppx@latest validate http://localhost:$PORT` to iteratively validate the implementation as you develop.
```

You can also follow the step-by-step guide below.

## Create a Stripe deposit address

Before you configure your server, create a crypto deposit address. This is the on-chain address where Tempo payments are sent.

```bash
curl https://api.stripe.com/v1/crypto/deposit_addresses \
  -u "$STRIPE_SECRET_KEY:" \
  -H "Stripe-Version: 2026-05-27.preview" \
  -d network=tempo
```

Store the returned address as your `DEPOSIT_ADDRESS` environment variable.

You can create deposit addresses as often as you want, but we recommend that you keep these calls off your core request path.

## Install dependencies

Install the required dependencies:

```bash
npm install mppx stripe
```

## Create your endpoint

Configure your server with both payment methods: Tempo, which uses the deposit address from the previous step, and Stripe for cards and Link. Add a single POST `/paid` endpoint that accepts both payment types with `Mppx.compose`. In this example, the endpoint charges 0.01 USD for crypto and 0.50 USD for fiat. When no credential is present, the server returns a `402` with both challenges, and the client selects the payment method it supports.

#### Node.js

```node
import crypto from 'crypto';
import Stripe from 'stripe';
// 'stripe' here is the mppx payment method factory, not the Stripe Node SDK. We alias it to avoid confusion.
import { Mppx, stripe as mppStripe, tempo } from 'mppx/server';

// USDC on Tempo (mainnet)
const TEMPO_USDC = '0x20c000000000000000000000b9537d11c60e8b50';

// Stripe deposit address from the previous step
const DEPOSIT_ADDRESS = process.env.DEPOSIT_ADDRESS as `0x${string}`;

// Secret used to secure payment challenges
// https://mpp.dev/protocol/challenges#challenge-binding
const mppSecretKey = crypto.createHmac("sha256", process.env.STRIPE_SECRET_KEY!).update("mpp-challenge-signing").digest("base64");

// Crypto PaymentIntents require API version 2026-05-27.preview or later.
const stripeClient = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2026-05-27.preview',
});

const mppx = Mppx.create({
  methods: [
    tempo.charge({
      currency: TEMPO_USDC,
      recipient: DEPOSIT_ADDRESS,
      decimals: 2,
    }),
    mppStripe.charge({
      client: stripeClient,
      networkId: process.env.STRIPE_PROFILE_ID!,
      paymentMethodTypes: ['card', 'link'],
      decimals: 2,
    }),
  ],
  secretKey: mppSecretKey,
});

export async function handler(request: Request) {
  const response = await Mppx.compose(
    mppx.tempo.charge({ amount: '0.01', recipient: DEPOSIT_ADDRESS }),
    mppx.stripe.charge({ amount: '0.50', currency: 'usd' }),
  )(request);

  if (response.status === 402) return response.challenge;

  return response.withReceipt(Response.json({ data: '...' }));
}
```

## Create a PaymentIntent

#### Crypto

Use the `onPaymentSuccess` hook to record each on-chain transaction as a [PaymentIntent](https://docs.stripe.com/api/payment_intents.md) using `transaction_verification` mode.

> #### API version
> 
> This feature requires the `2026-05-27.preview` API version. Set the `Stripe-Version` header to `2026-05-27.preview` when you initialize your Stripe client.

#### Node.js

```node
// Networks that settle on-chain and can be recorded with transaction_verification.
const SUPPORTED_NETWORKS = ['tempo'];

mppx.onPaymentSuccess(async ({ receipt, amount }) => {
  const txHash = receipt.reference;
  if (!txHash || !SUPPORTED_NETWORKS.includes(receipt.method)) {
    // Not an on-chain payment we can record, so skip it.
    return;
  }

  const amountInCents = Math.round(Number(amount) * 100);
  if (amountInCents < 1) {
    // Amount below 1¢, skip the PaymentIntent.
    return;
  }

  const pi = await stripeClient.paymentIntents.create(
    {
      amount: amountInCents,
      currency: 'usd',
      confirm: true,
      payment_method_data: { type: 'crypto' },
      payment_method_types: ['crypto'],
      payment_method_options: {
        crypto: {
          mode: 'transaction_verification',
          transaction_verification_options: {
            network: receipt.method,
            transaction_hash: txHash,
          },
        },
      },
    } as Stripe.PaymentIntentCreateParams,
    { idempotencyKey: txHash },
  );

  console.log(`Stripe PI ${pi.id}: ${amountInCents}¢ on ${receipt.method} for tx ${txHash}`);
});
```

#### SPT

> Stripe requires a minimum charge of 0.50 USD (or equivalent) for card payments via SPT.

With SPT payments, the server automatically creates the PaymentIntent when it receives a valid SPT credential from the client. The `stripe.charge` method handles the PaymentIntent creation using the SPT provided by the client.

The PaymentIntent includes:

- The amount and currency from the challenge
- The payment method from the SPT
- Any metadata configured in the `stripe.charge` method
- Settlement through Stripe’s payment rails

You don’t need to create the PaymentIntent separately. `stripe.charge` creates it when it validates the credential.

## Test your endpoint

### Test with `mppx validate`

Run `mppx validate` to automatically verify your implementation end-to-end. The command tests discovery, challenge formats, error handling, and the full payment flow.

```bash
npx mppx@latest validate http://localhost:4242
```

We recommend running `validate` against both a sandbox and live mode version of your server. In a sandbox, the CLI automatically completes roundtrip test transactions against your server. In live mode, the CLI can also complete roundtrip transactions with real funds.

### Test manually

You can also test each step individually. First, verify your server returns a `402` with the payment requirements:

#### Crypto

Use [mppx](https://www.npmjs.com/package/mppx) to send a Tempo payment from the command line. Because you created the deposit address in live mode, this command moves real funds.

```bash
curl -fsSL https://tempo.xyz/install | bash
tempo wallet login
tempo wallet fund
tempo request -X POST --json '{}' http://localhost:4242/paid
```

After a successful payment, the server returns the content. In the [Dashboard](https://dashboard.stripe.com), go to **Payments** to see the transaction.

#### SPT

Use the [link-cli](https://link.com/agents) to issue a test SPT for your account. The `link-cli` is a tool that can provision one-time shared payment token credentials using your Link account. Follow the instructions at [link.com/agents](https://link.com/agents) to install the `link-cli` skills or register it as an MCP server in your preferred agent.

To test with the `link-cli` manually, directly invoke its commands:

```bash
npx @stripe/link-cli auth login
```

```bash
npx @stripe/link-cli mpp pay http://localhost:4242/paid \
  -X POST \
  -d '{}' \
  --context "Testing machine payments integration on Stripe MPP using the link-cli on http://localhost:4242/paid"
```

In the [Dashboard](https://dashboard.stripe.com), go to **Payments** to see the transaction.

### Test with a Stripe sandbox

To test in a sandbox:

1. Configure your server to use your sandbox Stripe API key.
2. Create a Stripe profile for your sandbox and use the `profile_test_` ID.
3. Use Tempo’s `pathUSD` test currency, `0x20C0000000000000000000000000000000000000`.
4. Set `testnet: true` in your `tempo` configuration.
5. Create a Tempo deposit address in your sandbox account:

```bash
curl https://api.stripe.com/v1/crypto/deposit_addresses \
  -u "$STRIPE_SECRET_KEY:" \
  -H "Stripe-Version: 2026-05-27.preview" \
  -d network=tempo
```

#### Node.js

```node
const mppx = Mppx.create({
  methods: [
    tempo.charge({
      // pathUSD
      currency: '0x20C0000000000000000000000000000000000000',
      // Sandbox deposit address
      recipient: DEPOSIT_ADDRESS,
      // Testnet
      testnet: true,
      decimals: 2,
    }),
    mppStripe.charge({
      client: stripeClient,
      // Sandbox profile ID
      networkId: 'profile_test_...',
      paymentMethodTypes: ['card', 'link'],
      decimals: 2,
    }),
  ],
  secretKey: mppSecretKey,
});
```
