Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@
"@stripe/stripe-js": "^9.3.1",
"@vercel/blob": "^2.3.3",
"mermaid": "^11.14.0",
"mppx": "0.6.20",
"mppx": "0.6.27",
"react": "^19",
"react-dom": "^19",
"stripe": "^22.1.0",
"tailwindcss": "^4.2.4",
"viem": "^2.48.4",
"viem": "^2.50.4",
"vocs": "https://pkg.pr.new/vocs@425",
"wagmi": "^3.6.5",
"waku": "^1.0.0-alpha.8"
Expand Down
76 changes: 38 additions & 38 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/pages/blog/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ imageDescription: "Updates on the Machine Payments Protocol"
import { BlogPostList } from "../../components/BlogPostList";

<BlogPostList posts={[
{
date: "Thursday, May 21, 2026",
title: "Payment hooks",
description: <>Core SDKs now expose typed payment hooks for logging, monitoring, and request context.</>,
to: "/blog/payment-hooks",
},
{
date: "May 12, 2026",
title: "Subscriptions",
Expand Down
73 changes: 73 additions & 0 deletions src/pages/blog/payment-hooks.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
layout: minimal
outline: false
showAskAi: false
showFeedback: false
showSearch: false
description: "Core SDKs now expose typed payment hooks for logging, monitoring, and request context."
imageDescription: "Monitor request status with SDK hooks"
---

<div className="blog-narrow">

<a href="/blog" className="blog-back">Blog</a>

<p className="blog-date" style={{ color: 'var(--vocs-color_text3)', fontSize: '14px' }}>Thursday, May 21, 2026</p>

# Payment hooks [Observe MPP requests with typed lifecycle events]

MPP's core SDKs now expose typed payment hooks for client and server payment flows. Use them to record what happened around an MPP request without rewriting your payment handler.

## Why hooks matter

MPP puts the payment flow close to the request flow in REST APIs and MCP servers. That makes basic integration simple, but becomes fragile when used in production applications and existing tech stacks.

Hooks give you a typed place to attach that visibility:

- **Monitoring and observability**: Count `Challenge`s, successful payments, failed `Credential`s, and paid retry responses, then attach `Challenge` IDs, method names, intents, amounts, currencies, and `Receipt` references to traces.
- **Logging**: Record enough context to debug a failed payment without logging secrets.
- **Support**: Connect a user-facing request to the payment attempt that authorized it.

## What changed

Server hooks observe issued Challenges, successful payments, and rejected Credentials. Use typed helpers for common events, canonical strings for direct event names, or `*` for a single catch-all handler:

```ts twoslash [server.ts]
import { Mppx, tempo } from 'mppx/server'

const payment = Mppx.create({
methods: [tempo.charge()],
})

const log = (event: string, data: Record<string, unknown>) => {
console.log(event, data)
}

payment.onChallengeCreated(({ challenge, request }) => { // [!code hl]
log('payment.challenge.created', {
amount: request.amount,
challengeId: challenge.id,
})
})

payment.on('payment.success', ({ receipt, request }) => { // [!code hl]
log('payment.success', {
amount: request.amount,
reference: receipt.reference,
})
})

payment.onPaymentFailed(({ challenge, error, request }) => { // [!code hl]
log('payment.failed', {
amount: request.amount,
challengeId: challenge.id,
error: error.name,
})
})
```

## What's next

This release starts with core lifecycle events of the MPP request flow. If there is an event or payload field which you would like to monitor, please leave feedback on [GitHub](https://github.com/wevm/mppx/issues).

</div>
5 changes: 3 additions & 2 deletions src/pages/sdk/features.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@ This page tracks which features are implemented in each official SDK.
| Component | [TypeScript](https://github.com/wevm/mppx) | [Rust](https://github.com/tempoxyz/mpp-rs) | [Python](https://github.com/tempoxyz/pympp) | [Ruby](https://github.com/stripe/mpp-rb) |
|---|---|---|---|---|
| Client | ✓ | ✓ | ✓ | ✓ |
| Server | ✓ | ✓ | ✓ | ✓ |
| Event handling | ✓ | ✓ | ✓ | ✓ |
| Proxy | ✓ | ✓ | — | — |
| Server | ✓ | ✓ | ✓ | ✓ |

## Payment methods

| Method | TypeScript | Rust | Python | Ruby |
|---|---|---|---|---|
| [Tempo](/payment-methods/tempo) | ✓ | ✓ | ✓ | ✓ |
| [Stripe](/payment-methods/stripe) | ✓ | | | ✓ |
| [Stripe](/payment-methods/stripe) | ✓ | | | ✓ |

Additional payment methods implement their own SDKs. Refer to the maintaining organizations for support and availability.

Expand Down
1 change: 1 addition & 0 deletions src/pages/sdk/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { TypeScriptSdkCard, PythonSdkCard, RustSdkCard, GoSdkCard, RubySdkCard,
| **Server** | <Badge variant="success">✓</Badge> | <Badge variant="success">✓</Badge> | <Badge variant="success">✓</Badge> | <Badge variant="success">✓</Badge> | <Badge variant="success">✓</Badge> |
| **Core types** | <Badge variant="success">✓</Badge> | <Badge variant="success">✓</Badge> | <Badge variant="success">✓</Badge> | <Badge variant="success">✓</Badge> | <Badge variant="success">✓</Badge> |
| **Charge intent** | <Badge variant="success">✓</Badge> | <Badge variant="success">✓</Badge> | <Badge variant="success">✓</Badge> | <Badge variant="success">✓</Badge> | <Badge variant="success">✓</Badge> |
| **Event handling** | <Badge variant="success">✓</Badge> | <Badge variant="success">✓</Badge> | <Badge variant="success">✓</Badge> | <Badge variant="warning">—</Badge> | <Badge variant="success">✓</Badge> |
| **Session intent** | <Badge variant="success">✓</Badge> | <Badge variant="warning">—</Badge> | <Badge variant="success">✓</Badge> | <Badge variant="warning">—</Badge> | <Badge variant="warning">—</Badge> |
| **Stripe method** | <Badge variant="success">✓</Badge> | <Badge variant="success">✓</Badge> | <Badge variant="success">✓</Badge> | <Badge variant="warning">—</Badge> | <Badge variant="success">✓</Badge> |
| **Fee sponsorship** | <Badge variant="success">✓</Badge> | <Badge variant="success">✓</Badge> | <Badge variant="success">✓</Badge> | <Badge variant="success">✓</Badge> | <Badge variant="success">✓</Badge> |
Expand Down
44 changes: 44 additions & 0 deletions src/pages/sdk/python/client.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,50 @@ async with Client(methods=[tempo(account=account, intents={"charge": ChargeInten
print(response.json())
```

## Event handling

Register event handlers to record the automatic `402` flow. Use helper methods for named events, event constants for shared wiring, and `*` for every client event.

```python [client.py]
from mpp.client import Client
from mpp.events import PAYMENT_RESPONSE
from mpp.methods.tempo import tempo, TempoAccount, ChargeIntent

account = TempoAccount.from_key("0x...")

async with Client(methods=[tempo(account=account, intents={"charge": ChargeIntent()})]) as client:
def log(event: str, data: dict[str, object]) -> None:
print(event, data)

# Record the selected Challenge before the client creates a Credential.
client.on_challenge_received(
lambda payload: log(
"payment.challenge.received",
{
"challenge_id": payload["challenge"].id,
"intent": payload["challenge"].intent,
"method": payload["challenge"].method,
},
)
)

# Record the retried response after payment.
client.on(
PAYMENT_RESPONSE,
lambda payload: log(
"payment.response",
{"status": payload["response"].status_code},
),
)

# Catch every client payment event in one handler.
client.on("*", lambda event: log("payment.event", {"name": event.name}))

response = await client.get("https://api.example.com/resource")
```

`on_challenge_received` can return a `Credential` to override the default Credential creation path. Other handlers only record payment handling. Each registration returns an unsubscribe function.

## Common methods

| Method | Description |
Expand Down
Loading
Loading