From d15c1d760a9c2d7d5c33eec69ab0d9ecb9daec05 Mon Sep 17 00:00:00 2001 From: Brendan Ryan <1572504+brendanjryan@users.noreply.github.com> Date: Fri, 15 May 2026 15:21:59 -0700 Subject: [PATCH 1/7] docs: update mppx hooks documentation --- package.json | 2 +- pnpm-lock.yaml | 10 +- src/pages/blog/index.mdx | 6 + src/pages/blog/payment-hooks.mdx | 152 ++++++++++++++++++ .../sdk/typescript/client/Fetch.from.mdx | 6 + .../sdk/typescript/client/Fetch.polyfill.mdx | 6 + .../sdk/typescript/client/Mppx.create.mdx | 76 +++++++++ .../sdk/typescript/server/Mppx.create.mdx | 26 ++- 8 files changed, 277 insertions(+), 7 deletions(-) create mode 100644 src/pages/blog/payment-hooks.mdx diff --git a/package.json b/package.json index 2a81cbcc..2e361f1f 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "@stripe/stripe-js": "^9.3.1", "@vercel/blob": "^2.3.3", "mermaid": "^11.14.0", - "mppx": "0.6.20", + "mppx": "0.6.24", "react": "^19", "react-dom": "^19", "stripe": "^22.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bba8f342..161d2fda 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,8 +29,8 @@ importers: specifier: ^11.14.0 version: 11.14.0 mppx: - specifier: 0.6.20 - version: 0.6.20(@modelcontextprotocol/sdk@1.27.1(@cfworker/json-schema@4.1.1)(zod@4.3.6))(express@5.2.1)(hono@4.12.18)(typescript@6.0.3)(viem@2.48.4(typescript@6.0.3)(zod@4.3.6)) + specifier: 0.6.24 + version: 0.6.24(@modelcontextprotocol/sdk@1.27.1(@cfworker/json-schema@4.1.1)(zod@4.3.6))(express@5.2.1)(hono@4.12.18)(typescript@6.0.3)(viem@2.48.4(typescript@6.0.3)(zod@4.3.6)) react: specifier: ^19 version: 19.2.5 @@ -3022,8 +3022,8 @@ packages: mlly@1.8.2: resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} - mppx@0.6.20: - resolution: {integrity: sha512-RNXcY64QQVuDo3BqFEQAfp8ciF9O+DVIazDzZsf8vd9g4xqO1BRvTBLEcSO0WcEo6HcmQSL3Qsv3zgdM8vb3Eg==} + mppx@0.6.24: + resolution: {integrity: sha512-3vqZ4Xvn5XcTQQFNOgLaROkjOibrkMOeX15nMYwwOsWhXVuSDfitZfDj+Mr1sqPkbHyfQqDrpq2b7CyZWsp0Ww==} hasBin: true peerDependencies: '@modelcontextprotocol/sdk': '>=1.25.0' @@ -7226,7 +7226,7 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.3 - mppx@0.6.20(@modelcontextprotocol/sdk@1.27.1(@cfworker/json-schema@4.1.1)(zod@4.3.6))(express@5.2.1)(hono@4.12.18)(typescript@6.0.3)(viem@2.48.4(typescript@6.0.3)(zod@4.3.6)): + mppx@0.6.24(@modelcontextprotocol/sdk@1.27.1(@cfworker/json-schema@4.1.1)(zod@4.3.6))(express@5.2.1)(hono@4.12.18)(typescript@6.0.3)(viem@2.48.4(typescript@6.0.3)(zod@4.3.6)): dependencies: incur: 0.4.5 ox: 0.14.20(typescript@6.0.3)(zod@4.4.3) diff --git a/src/pages/blog/index.mdx b/src/pages/blog/index.mdx index 0593a657..bdbc7404 100644 --- a/src/pages/blog/index.mdx +++ b/src/pages/blog/index.mdx @@ -15,6 +15,12 @@ imageDescription: "Updates on the Machine Payments Protocol" import { BlogPostList } from "../../components/BlogPostList"; SDKs now expose typed payment hooks for logging, monitoring, and request context., + to: "/blog/payment-hooks", + }, { date: "May 12, 2026", title: "Subscriptions", diff --git a/src/pages/blog/payment-hooks.mdx b/src/pages/blog/payment-hooks.mdx new file mode 100644 index 00000000..9dc1c9eb --- /dev/null +++ b/src/pages/blog/payment-hooks.mdx @@ -0,0 +1,152 @@ +--- +layout: minimal +outline: false +showAskAi: false +showFeedback: false +showSearch: false +description: "SDKs now expose typed payment hooks for logging, monitoring, and request context." +imageDescription: "Monitor request status with SDK hooks" +--- + +
+ +Blog + +

May 18, 2026

+ +# Payment hooks [Observe MPP requests with typed lifecycle events] + +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. + +Hooks are useful when payment telemetry belongs next to the rest of your application telemetry: logs, metrics, traces, audit records, support dashboards, or local debugging context. + +## What changed + +Client hooks observe Challenge selection, Credential creation, retry responses, and failures. Use typed helpers for common events, canonical strings for direct event names, or `*` for a single catch-all handler: + +```ts twoslash [client.ts] +import { Mppx, tempo } from 'mppx/client' +import { privateKeyToAccount } from 'viem/accounts' + +const account = privateKeyToAccount(process.env.MPP_PRIVATE_KEY as `0x${string}`) + +const mppx = Mppx.create({ + methods: [tempo({ account })], + polyfill: false, +}) + +const log = (event: string, data: Record) => { + console.log(event, data) +} + +mppx.onChallengeReceived(({ challenge }) => { + // Observe the selected Challenge before the SDK creates a Credential. + log('payment.challenge.received', { + challengeId: challenge.id, + intent: challenge.intent, + method: challenge.method, + }) + return undefined +}) + +mppx.on('payment.response', ({ challenge, response }) => { + // Use canonical event names when you want to share event wiring. + log('payment.response', { + challengeId: challenge.id, + status: response.status, + }) +}) + +mppx.on('*', ({ name, payload }) => { + // Use `*` to send all payment events through one telemetry path. + log('payment.event', { + hasChallenge: 'challenge' in payload, + name, + }) +}) + +mppx.onPaymentFailed(({ error, input }) => { + // Capture failures from Challenge parsing, Credential creation, or retry handling. + log('payment.failed', { + error: error instanceof Error ? error.message : String(error), + input: String(input), + }) +}) + +const response = await mppx.fetch('https://api.example.com/report') +``` + +Server hooks observe issued Challenges, successful payments, and rejected Credentials: + +```ts twoslash [server.ts] +import { Mppx, tempo } from 'mppx/server' + +const payment = Mppx.create({ + methods: [tempo.charge()], +}) + +const log = (event: string, data: Record) => { + console.log(event, data) +} + +payment.onChallengeCreated(({ challenge, request }) => { + // Observe each `402` Challenge before it is returned to the client. + log('payment.challenge.created', { + amount: request.amount, + challengeId: challenge.id, + currency: request.currency, + }) +}) + +payment.on('payment.success', ({ receipt, request }) => { + // Use canonical event names when you want to share event wiring. + log('payment.success', { + amount: request.amount, + currency: request.currency, + reference: receipt.reference, + status: receipt.status, + }) +}) + +payment.on('*', ({ name, payload }) => { + // Use `*` to send all payment events through one telemetry path. + log('payment.event', { + challengeId: payload.challenge.id, + intent: payload.method.intent, + name, + }) +}) + +payment.onPaymentFailed(({ challenge, error, request }) => { + // Record failed Credential verification with request and Challenge context. + log('payment.failed', { + amount: request.amount, + challengeId: challenge.id, + error: error.name, + }) +}) +``` + +## Why hooks matter + +MPP keeps the payment flow close to the request flow. That makes integration simple, but production systems still need visibility. + +Hooks give you a typed place to attach that visibility: + +- **Monitoring and observability**: Count Challenges, successful payments, failed Credentials, 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. + +## Use hooks carefully + +Client observation hooks don't change payment handling. If a client observation hook throws, `mppx` ignores the error and continues the payment flow. + +`onChallengeReceived` is the one client hook that can affect handling: return a non-empty Credential string to use it for the retry. This lets advanced clients create Credentials with custom context before falling back to `onChallenge` or the default flow. + +Server hooks run inline on the payment request path. Keep them short, or hand work to your logger, metrics client, or queue. `mppx` ignores thrown server hook errors so observability code doesn't block payment verification, but slow hooks still delay the response. + +## What's next + +This release starts with core lifecycle events. If there is an event or payload field you need, leave feedback on [GitHub](https://github.com/wevm/mppx/issues). + +
diff --git a/src/pages/sdk/typescript/client/Fetch.from.mdx b/src/pages/sdk/typescript/client/Fetch.from.mdx index 73718926..3188f5a0 100644 --- a/src/pages/sdk/typescript/client/Fetch.from.mdx +++ b/src/pages/sdk/typescript/client/Fetch.from.mdx @@ -68,6 +68,12 @@ Controls when `Accept-Payment` is injected. Use `'same-origin'` in browsers when you only want same-origin payment discovery. Use `{ origins }` when paid APIs live on specific origins. Origin patterns support `*.` subdomain wildcards. +### eventDispatcher (optional) + +- **Type:** `ClientEventDispatcher` + +Advanced shared event dispatcher. `challenge.received` handlers run before `onChallenge`; the first non-empty Credential string skips `onChallenge`. + ### fetch (optional) - **Type:** `typeof globalThis.fetch` diff --git a/src/pages/sdk/typescript/client/Fetch.polyfill.mdx b/src/pages/sdk/typescript/client/Fetch.polyfill.mdx index 4a0d47a6..0b0930ca 100644 --- a/src/pages/sdk/typescript/client/Fetch.polyfill.mdx +++ b/src/pages/sdk/typescript/client/Fetch.polyfill.mdx @@ -63,6 +63,12 @@ Resolved `Accept-Payment` header and preference data. Controls when `Accept-Payment` is injected. +### eventDispatcher (optional) + +- **Type:** `ClientEventDispatcher` + +Advanced shared event dispatcher. `challenge.received` handlers run before `onChallenge`; the first non-empty Credential string skips `onChallenge`. + ### fetch (optional) - **Type:** `typeof globalThis.fetch` diff --git a/src/pages/sdk/typescript/client/Mppx.create.mdx b/src/pages/sdk/typescript/client/Mppx.create.mdx index d22492e6..39df45c2 100644 --- a/src/pages/sdk/typescript/client/Mppx.create.mdx +++ b/src/pages/sdk/typescript/client/Mppx.create.mdx @@ -18,6 +18,37 @@ Mppx.create({ const res = await fetch('https://mpp.dev/api/ping/paid') ``` +### With payment hooks + +Register hooks on the returned `mppx` instance to observe the payment flow. + +```ts twoslash +import { Mppx, tempo } from 'mppx/client' +import { privateKeyToAccount } from 'viem/accounts' + +const account = privateKeyToAccount('0x...') + +const mppx = Mppx.create({ + methods: [tempo({ account })], + polyfill: false, +}) + +const offFailure = mppx.onPaymentFailed(({ error, input }) => { + console.error('payment failed:', input, error) +}) + +const offResponse = mppx.onPaymentResponse(({ challenge, response }) => { + console.log('payment response:', challenge.id, response.status) +}) + +const res = await mppx.fetch('https://mpp.dev/api/ping/paid') +console.log(res.status) +// @log: 200 + +offFailure() +offResponse() +``` + ### Without polyfill Set `polyfill: false` to get a scoped fetch without modifying the global: @@ -103,6 +134,16 @@ type Mppx = { context?: Context, options?: { acceptPayment?: string | AcceptPayment.Entry[] }, ) => Promise + /** Register a handler for any client payment event. */ + on(name: ClientEventName | '*', handler: ClientEventHandler): Unsubscribe + /** Register a handler for received payment Challenges. */ + onChallengeReceived(handler: ChallengeReceivedHandler): Unsubscribe + /** Register a handler for created Credentials. */ + onCredentialCreated(handler: CredentialCreatedHandler): Unsubscribe + /** Register a handler for failed automatic payment handling. */ + onPaymentFailed(handler: PaymentFailedHandler): Unsubscribe + /** Register a handler for payment retry responses. */ + onPaymentResponse(handler: PaymentResponseHandler): Unsubscribe } ``` @@ -125,6 +166,41 @@ const mppx = Mppx.create({ const raw = await mppx.rawFetch('https://api.example.com/ws-auth') // [!code focus] ``` +## Payment hooks + +Payment hooks are for logging, monitoring, tracing, and request-local context. Each registration returns an unsubscribe function. + +| Hook | Runs when | +|---|---| +| `onChallengeReceived` | A `402` Challenge is selected | +| `onCredentialCreated` | A Credential is created for the selected Challenge | +| `onPaymentResponse` | The retry after payment returns a successful response | +| `onPaymentFailed` | Challenge parsing, credential creation, or retry handling fails | +| `on('*', handler)` | Any client payment event fires | + +`onChallengeReceived` runs before `onChallenge`. It can return a non-empty Credential string to override the default credential flow. Other hooks are observers: thrown errors are ignored and don't change payment handling. + +```ts twoslash +import { Mppx, tempo } from 'mppx/client' +import { privateKeyToAccount } from 'viem/accounts' + +const account = privateKeyToAccount('0x...') + +const mppx = Mppx.create({ + methods: [tempo({ account })], + polyfill: false, +}) + +mppx.onChallengeReceived(async ({ challenge, createCredential }) => { + console.log('challenge received:', challenge.id) + return createCredential() +}) + +mppx.on('*', ({ name }) => { + console.log('payment event:', name) +}) +``` + ## Parameters ### acceptPaymentPolicy (optional) diff --git a/src/pages/sdk/typescript/server/Mppx.create.mdx b/src/pages/sdk/typescript/server/Mppx.create.mdx index 9e72e8f5..bbe460c9 100644 --- a/src/pages/sdk/typescript/server/Mppx.create.mdx +++ b/src/pages/sdk/typescript/server/Mppx.create.mdx @@ -25,6 +25,30 @@ const payment = Mppx.create({ }) ``` +### With payment hooks + +Register hooks on the returned `payment` instance to observe Challenges, successful payments, and failures. + +```ts twoslash +import { Mppx, tempo } from 'mppx/server' + +const payment = Mppx.create({ + methods: [tempo.charge()], +}) + +payment.onChallengeCreated(({ challenge, request }) => { + console.log('challenge created:', challenge.id, request.amount) +}) + +payment.onPaymentFailed(({ challenge, error }) => { + console.error('payment failed:', challenge.id, error.name) +}) + +payment.onPaymentSuccess(({ receipt, request }) => { + console.log('payment success:', receipt.reference, request.amount) +}) +``` + ## Return type ```ts @@ -34,7 +58,7 @@ import type { Mppx, Transport } from 'mppx/server' type ReturnType = Mppx<[Method.Server], Transport.Http> ``` -The returned object includes the method's intent functions (for example, `charge`), `compose`, and `verifyCredential`. +The returned object includes the method's intent functions (for example, `charge`), `challenge`, `compose`, payment hooks, and `verifyCredential`. ## Parameters From d14f2725b622e4ead1b5b0ac762fb80a2edab8ff Mon Sep 17 00:00:00 2001 From: Brendan Ryan <1572504+brendanjryan@users.noreply.github.com> Date: Mon, 18 May 2026 11:35:21 -0700 Subject: [PATCH 2/7] docs: update sdk event handling --- package.json | 2 +- pnpm-lock.yaml | 10 +++---- src/pages/sdk/features.mdx | 5 ++-- src/pages/sdk/index.mdx | 1 + src/pages/sdk/python/client.mdx | 44 ++++++++++++++++++++++++++++ src/pages/sdk/python/server.mdx | 51 +++++++++++++++++++++++++++++++++ src/pages/sdk/rust/client.mdx | 40 +++++++++++++++++++++++++- src/pages/sdk/rust/index.mdx | 8 +++--- src/pages/sdk/rust/server.mdx | 30 +++++++++++++++++++ 9 files changed, 178 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 2e361f1f..04e8c2ff 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "@stripe/stripe-js": "^9.3.1", "@vercel/blob": "^2.3.3", "mermaid": "^11.14.0", - "mppx": "0.6.24", + "mppx": "0.6.25", "react": "^19", "react-dom": "^19", "stripe": "^22.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 161d2fda..3d934396 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,8 +29,8 @@ importers: specifier: ^11.14.0 version: 11.14.0 mppx: - specifier: 0.6.24 - version: 0.6.24(@modelcontextprotocol/sdk@1.27.1(@cfworker/json-schema@4.1.1)(zod@4.3.6))(express@5.2.1)(hono@4.12.18)(typescript@6.0.3)(viem@2.48.4(typescript@6.0.3)(zod@4.3.6)) + specifier: 0.6.25 + version: 0.6.25(@modelcontextprotocol/sdk@1.27.1(@cfworker/json-schema@4.1.1)(zod@4.3.6))(express@5.2.1)(hono@4.12.18)(typescript@6.0.3)(viem@2.48.4(typescript@6.0.3)(zod@4.3.6)) react: specifier: ^19 version: 19.2.5 @@ -3022,8 +3022,8 @@ packages: mlly@1.8.2: resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} - mppx@0.6.24: - resolution: {integrity: sha512-3vqZ4Xvn5XcTQQFNOgLaROkjOibrkMOeX15nMYwwOsWhXVuSDfitZfDj+Mr1sqPkbHyfQqDrpq2b7CyZWsp0Ww==} + mppx@0.6.25: + resolution: {integrity: sha512-d7+NdH9fl2usi2+I4j7zqqR2eSf3WFGCFHdzRMW7i9ikKsPwgAz2jRtlD4ZV9CHBIL16518uJuLFaMAWUn4sCQ==} hasBin: true peerDependencies: '@modelcontextprotocol/sdk': '>=1.25.0' @@ -7226,7 +7226,7 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.3 - mppx@0.6.24(@modelcontextprotocol/sdk@1.27.1(@cfworker/json-schema@4.1.1)(zod@4.3.6))(express@5.2.1)(hono@4.12.18)(typescript@6.0.3)(viem@2.48.4(typescript@6.0.3)(zod@4.3.6)): + mppx@0.6.25(@modelcontextprotocol/sdk@1.27.1(@cfworker/json-schema@4.1.1)(zod@4.3.6))(express@5.2.1)(hono@4.12.18)(typescript@6.0.3)(viem@2.48.4(typescript@6.0.3)(zod@4.3.6)): dependencies: incur: 0.4.5 ox: 0.14.20(typescript@6.0.3)(zod@4.4.3) diff --git a/src/pages/sdk/features.mdx b/src/pages/sdk/features.mdx index ad5f80eb..c3a53137 100644 --- a/src/pages/sdk/features.mdx +++ b/src/pages/sdk/features.mdx @@ -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. diff --git a/src/pages/sdk/index.mdx b/src/pages/sdk/index.mdx index 3bc8d346..8114d2cb 100644 --- a/src/pages/sdk/index.mdx +++ b/src/pages/sdk/index.mdx @@ -25,6 +25,7 @@ import { TypeScriptSdkCard, PythonSdkCard, RustSdkCard, GoSdkCard, RubySdkCard, | **Server** | | | | | | | **Core types** | | | | | | | **Charge intent** | | | | | | +| **Event handling** | | | | | | | **Session intent** | | | | | | | **Stripe method** | | | | | | | **Fee sponsorship** | | | | | | diff --git a/src/pages/sdk/python/client.mdx b/src/pages/sdk/python/client.mdx index e0613e1e..a7aa23b1 100644 --- a/src/pages/sdk/python/client.mdx +++ b/src/pages/sdk/python/client.mdx @@ -21,6 +21,50 @@ async with Client(methods=[tempo(account=account, intents={"charge": ChargeInten print(response.json()) ``` +## Event handling + +Register event handlers to log or trace the automatic `402` flow. Typed helpers cover common events, string names map to the lifecycle event names, and `*` receives 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) + + # Observe 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, + }, + ) + ) + + # Observe 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 observe payment handling. Each registration returns an unsubscribe function. + ## Common methods | Method | Description | diff --git a/src/pages/sdk/python/server.mdx b/src/pages/sdk/python/server.mdx index b3c22c4e..cbd85396 100644 --- a/src/pages/sdk/python/server.mdx +++ b/src/pages/sdk/python/server.mdx @@ -62,6 +62,57 @@ mpp = Mpp.create( ) ``` +## Event handling + +Register event handlers to monitor issued Challenges, successful payments, and rejected Credentials. Typed helpers cover common events, string names map to the lifecycle event names, and `*` receives every server event. + +```python [server.py] +from mpp.events import PAYMENT_SUCCESS +from mpp.methods.tempo import tempo, ChargeIntent +from mpp.server import Mpp + +mpp = Mpp.create( + method=tempo( + currency="0x20c0000000000000000000000000000000000000", + intents={"charge": ChargeIntent()}, + recipient="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + ), + secret_key="my-server-secret", +) + +def log(event: str, data: dict[str, object]) -> None: + print(event, data) + +# Observe each Challenge issued by your API. +mpp.on_challenge_created( + lambda payload: log( + "payment.challenge.created", + { + "amount": payload["request"]["amount"], + "currency": payload["request"]["currency"], + "method": payload["method"], + }, + ) +) + +# Observe successful Credential verification. +mpp.on( + PAYMENT_SUCCESS, + lambda payload: log( + "payment.success", + { + "intent": payload["intent"], + "reference": payload["receipt"].reference, + }, + ), +) + +# Catch every server payment event in one handler. +mpp.on("*", lambda event: log("payment.event", {"name": event.name})) +``` + +Server handlers run inline on the payment request path. Keep them short, or send work to your logger, metrics client, or queue. Each registration returns an unsubscribe function. + ## `tempo()` factory `tempo()` creates a `TempoMethod` that bundles a payment network, its intents, and default parameters together. Each intent must be explicitly registered via the `intents` parameter. diff --git a/src/pages/sdk/rust/client.mdx b/src/pages/sdk/rust/client.mdx index 245cd8d3..e1778d2f 100644 --- a/src/pages/sdk/rust/client.mdx +++ b/src/pages/sdk/rust/client.mdx @@ -58,7 +58,7 @@ let provider = TempoProvider::new(signer, "https://rpc.tempo.xyz")? For automatic `402` handling on all requests, use `PaymentMiddleware` with `reqwest-middleware`. Requires the `middleware` feature. ```toml -mpp = { version = "0.1", features = ["tempo", "client", "middleware"] } +mpp = { version = "0.10.3", features = ["tempo", "client", "middleware"] } ``` ```rust @@ -75,6 +75,44 @@ let client = ClientBuilder::new(reqwest::Client::new()) let response = client.get("https://api.example.com/paid").send().await?; ``` +## Event handling + +Use `ClientEvents` to observe the automatic `402` flow. Typed helpers cover common events, `on()` registers a specific event kind, and `on_any()` receives every client event. + +```rust +use mpp::client::{ + ClientEvent, ClientEventKind, ClientEvents, PaymentMiddleware, TempoProvider, +}; +use reqwest_middleware::ClientBuilder; + +let provider = TempoProvider::new(signer, "https://rpc.tempo.xyz")?; +let events = ClientEvents::default(); + +// Observe the selected Challenge before the provider creates a Credential. +let _challenge = events.on_challenge_received(|ctx| async move { + println!("challenge received: {}", ctx.challenge.id); + None +}); + +// Observe the retried response after payment. +let _response = events.on(ClientEventKind::PaymentResponse, |event| async move { + if let ClientEvent::PaymentResponse(ctx) = event { + println!("payment response: {}", ctx.status); + } +}); + +// Catch every client payment event in one handler. +let _all = events.on_any(|event| async move { + println!("payment event: {}", event.kind().as_str()); +}); + +let client = ClientBuilder::new(reqwest::Client::new()) + .with(PaymentMiddleware::new(provider).with_events(events)) + .build(); +``` + +`on_challenge_received` can return `Some(PaymentCredential)` to override the default provider payment flow. Other handlers only observe payment handling. Keep the returned subscription handles alive while handlers should remain registered. + ## Multiple providers `MultiProvider` wraps multiple payment providers and picks the right one based on the challenge's `method` and `intent`: diff --git a/src/pages/sdk/rust/index.mdx b/src/pages/sdk/rust/index.mdx index 3fa8d668..0edf220b 100644 --- a/src/pages/sdk/rust/index.mdx +++ b/src/pages/sdk/rust/index.mdx @@ -89,16 +89,16 @@ let response = reqwest::Client::new() ```toml # Client only -mpp = { version = "0.1", features = ["tempo", "client"] } +mpp = { version = "0.10.3", features = ["tempo", "client"] } # Server only -mpp = { version = "0.1", features = ["tempo", "server"] } +mpp = { version = "0.10.3", features = ["tempo", "server"] } # Both -mpp = { version = "0.1", features = ["tempo", "client", "server"] } +mpp = { version = "0.10.3", features = ["tempo", "client", "server"] } # With middleware -mpp = { version = "0.1", features = ["tempo", "client", "middleware"] } +mpp = { version = "0.10.3", features = ["tempo", "client", "middleware"] } ``` ## Next steps diff --git a/src/pages/sdk/rust/server.mdx b/src/pages/sdk/rust/server.mdx index ec0aba49..1c757b46 100644 --- a/src/pages/sdk/rust/server.mdx +++ b/src/pages/sdk/rust/server.mdx @@ -204,6 +204,36 @@ let receipt = mpp .await?; ``` +## Event handling + +Register event handlers to observe successful Credential verification. Use the typed helper for `payment.success`, `on()` for a specific event kind, or `on_any()` to receive every server event. + +```rust +use mpp::server::{Mpp, ServerEvent, ServerEventKind, TempoConfig, tempo}; + +let mpp = Mpp::create(tempo(TempoConfig { + recipient: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", +}))?; + +// Observe successful Credential verification. +let _success = mpp.on_payment_success(|ctx| async move { + println!("payment success: {}", ctx.receipt.reference); +}); + +// Register by event kind when you want direct event access. +let _by_kind = mpp.on(ServerEventKind::PaymentSuccess, |event| async move { + let ServerEvent::PaymentSuccess(ctx) = event; + println!("payment method: {}", ctx.method); +}); + +// Catch every server payment event in one handler. +let _all = mpp.on_any(|event| async move { + println!("payment event: {}", event.kind().as_str()); +}); +``` + +Server handlers run inline with verification. Keep the returned subscription handles alive while handlers should remain registered. + ## Axum extractor The `MppCharge` extractor handles the full `402` challenge/verify flow automatically. Requires the `axum` feature. From d74b2e268977bc7e0ea6b58107d813bdcff286ac Mon Sep 17 00:00:00 2001 From: Brendan Ryan <1572504+brendanjryan@users.noreply.github.com> Date: Mon, 18 May 2026 17:06:45 -0700 Subject: [PATCH 3/7] docs: add ruby event handling --- src/pages/sdk/features.mdx | 2 +- src/pages/sdk/index.mdx | 2 +- src/pages/sdk/ruby/client.mdx | 44 ++++++++++++++++++++++++++++++ src/pages/sdk/ruby/server.mdx | 50 +++++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 2 deletions(-) diff --git a/src/pages/sdk/features.mdx b/src/pages/sdk/features.mdx index c3a53137..73d01bb8 100644 --- a/src/pages/sdk/features.mdx +++ b/src/pages/sdk/features.mdx @@ -13,7 +13,7 @@ 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 | ✓ | ✓ | ✓ | ✓ | -| Event handling | ✓ | ✓ | ✓ | — | +| Event handling | ✓ | ✓ | ✓ | ✓ | | Proxy | ✓ | ✓ | — | — | | Server | ✓ | ✓ | ✓ | ✓ | diff --git a/src/pages/sdk/index.mdx b/src/pages/sdk/index.mdx index 8114d2cb..08c9712f 100644 --- a/src/pages/sdk/index.mdx +++ b/src/pages/sdk/index.mdx @@ -25,7 +25,7 @@ import { TypeScriptSdkCard, PythonSdkCard, RustSdkCard, GoSdkCard, RubySdkCard, | **Server** | | | | | | | **Core types** | | | | | | | **Charge intent** | | | | | | -| **Event handling** | | | | | | +| **Event handling** | | | | | | | **Session intent** | | | | | | | **Stripe method** | | | | | | | **Fee sponsorship** | | | | | | diff --git a/src/pages/sdk/ruby/client.mdx b/src/pages/sdk/ruby/client.mdx index abc3b695..328edb30 100644 --- a/src/pages/sdk/ruby/client.mdx +++ b/src/pages/sdk/ruby/client.mdx @@ -28,6 +28,50 @@ When the server returns `402`, the client: 2. Calls `create_credential` on the matching method to sign a stablecoin transfer 3. Retries the request with the Credential in the `Authorization` header +## Event handling + +Register event handlers to log or trace the automatic `402` flow. Typed helpers cover common events, event constants map to the lifecycle event names, and `*` receives every client event. + +```ruby [client.rb] +require "json" +require "mpp-rb" + +account = Mpp::Methods::Tempo::Account.from_key("0x...") + +transport = Mpp::Client::Transport.new( + methods: [Mpp::Methods::Tempo.tempo(account: account)], +) + +log = ->(event, data) { puts({ event: event, **data }.to_json) } + +# Observe the selected Challenge before the client creates a Credential. +transport.on_challenge_received do |payload| + log.call( + "payment.challenge.received", + { + challenge_id: payload[:challenge].id, + intent: payload[:challenge].intent, + method: payload[:challenge].method, + }, + ) + nil +end + +# Observe the retried response after payment. +transport.on(Mpp::Events::PAYMENT_RESPONSE) do |payload| + log.call("payment.response", { status: payload[:response].code }) +end + +# Catch every client payment event in one handler. +transport.on("*") do |event| + log.call("payment.event", { name: event.name }) +end + +response = transport.get("https://api.example.com/resource") +``` + +`on_challenge_received` can return a `Credential` or `Payment` authorization string to override the default Credential creation path. Other handlers only observe payment handling. Each registration returns an unsubscribe proc. + ## Common methods | Method | Description | diff --git a/src/pages/sdk/ruby/server.mdx b/src/pages/sdk/ruby/server.mdx index 8cd6181b..ce2f580d 100644 --- a/src/pages/sdk/ruby/server.mdx +++ b/src/pages/sdk/ruby/server.mdx @@ -50,6 +50,56 @@ server = Mpp.create( ) ``` +## Event handling + +Register event handlers to monitor issued Challenges, successful payments, and rejected Credentials. Typed helpers cover common events, event constants map to the lifecycle event names, and `*` receives every server event. + +```ruby [server.rb] +require "json" +require "mpp-rb" + +server = Mpp.create( + method: Mpp::Methods::Tempo.tempo( + currency: "0x20c0000000000000000000000000000000000000", + intents: { "charge" => Mpp::Methods::Tempo::ChargeIntent.new }, + recipient: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + ), + secret_key: "my-server-secret", +) + +log = ->(event, data) { puts({ event: event, **data }.to_json) } + +# Observe each Challenge issued by your API. +server.on_challenge_created do |payload| + log.call( + "payment.challenge.created", + { + amount: payload[:request]["amount"], + currency: payload[:request]["currency"], + method: payload[:method][:name], + }, + ) +end + +# Observe successful Credential verification. +server.on(Mpp::Events::PAYMENT_SUCCESS) do |payload| + log.call( + "payment.success", + { + intent: payload[:method][:intent], + reference: payload[:receipt].reference, + }, + ) +end + +# Catch every server payment event in one handler. +server.on("*") do |event| + log.call("payment.event", { name: event.name }) +end +``` + +Server handlers run inline on the payment request path. Keep them short, or send work to your logger, metrics client, or queue. Each registration returns an unsubscribe proc. + ## `Mpp::Methods::Tempo.tempo()` factory `tempo()` creates a method that bundles a payment network, its intents, and default parameters together. Each intent must be explicitly registered via the `intents` parameter. From 91089fa0f39574b946217a62984b33c8d6df0d6e Mon Sep 17 00:00:00 2001 From: Brendan Ryan <1572504+brendanjryan@users.noreply.github.com> Date: Mon, 18 May 2026 17:19:41 -0700 Subject: [PATCH 4/7] docs: tighten payment hooks copy --- src/pages/blog/index.mdx | 2 +- src/pages/blog/payment-hooks.mdx | 40 ++++++++++++-------------------- src/pages/sdk/python/client.mdx | 8 +++---- src/pages/sdk/python/server.mdx | 6 ++--- src/pages/sdk/ruby/client.mdx | 8 +++---- src/pages/sdk/ruby/server.mdx | 6 ++--- src/pages/sdk/rust/client.mdx | 8 +++---- src/pages/sdk/rust/server.mdx | 6 ++--- 8 files changed, 37 insertions(+), 47 deletions(-) diff --git a/src/pages/blog/index.mdx b/src/pages/blog/index.mdx index bdbc7404..7e480c9d 100644 --- a/src/pages/blog/index.mdx +++ b/src/pages/blog/index.mdx @@ -16,7 +16,7 @@ import { BlogPostList } from "../../components/BlogPostList"; SDKs now expose typed payment hooks for logging, monitoring, and request context., to: "/blog/payment-hooks", diff --git a/src/pages/blog/payment-hooks.mdx b/src/pages/blog/payment-hooks.mdx index 9dc1c9eb..d862ed67 100644 --- a/src/pages/blog/payment-hooks.mdx +++ b/src/pages/blog/payment-hooks.mdx @@ -12,17 +12,25 @@ imageDescription: "Monitor request status with SDK hooks" Blog -

May 18, 2026

+

Wednesday, May 20, 2026

# Payment hooks [Observe MPP requests with typed lifecycle events] -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. +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. -Hooks are useful when payment telemetry belongs next to the rest of your application telemetry: logs, metrics, traces, audit records, support dashboards, or local debugging context. +## Why hooks matter + +MPP puts the payment flow close to the request flow. 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 -Client hooks observe Challenge selection, Credential creation, retry responses, and failures. Use typed helpers for common events, canonical strings for direct event names, or `*` for a single catch-all handler: +Client hooks observe the entire MPP payment lifecycle. Use typed helpers for common events, canonical strings for direct event names, or `*` for a single catch-all handler: ```ts twoslash [client.ts] import { Mppx, tempo } from 'mppx/client' @@ -40,7 +48,7 @@ const log = (event: string, data: Record) => { } mppx.onChallengeReceived(({ challenge }) => { - // Observe the selected Challenge before the SDK creates a Credential. + // Observe the selected Challenge before the SDK creates a credential. log('payment.challenge.received', { challengeId: challenge.id, intent: challenge.intent, @@ -66,14 +74,14 @@ mppx.on('*', ({ name, payload }) => { }) mppx.onPaymentFailed(({ error, input }) => { - // Capture failures from Challenge parsing, Credential creation, or retry handling. + // Capture failures from challenge parsing, credential creation, or retry handling. log('payment.failed', { error: error instanceof Error ? error.message : String(error), input: String(input), }) }) -const response = await mppx.fetch('https://api.example.com/report') +const response = await mppx.fetch('https://mpp.dev/api/ping/paid') ``` Server hooks observe issued Challenges, successful payments, and rejected Credentials: @@ -127,24 +135,6 @@ payment.onPaymentFailed(({ challenge, error, request }) => { }) ``` -## Why hooks matter - -MPP keeps the payment flow close to the request flow. That makes integration simple, but production systems still need visibility. - -Hooks give you a typed place to attach that visibility: - -- **Monitoring and observability**: Count Challenges, successful payments, failed Credentials, 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. - -## Use hooks carefully - -Client observation hooks don't change payment handling. If a client observation hook throws, `mppx` ignores the error and continues the payment flow. - -`onChallengeReceived` is the one client hook that can affect handling: return a non-empty Credential string to use it for the retry. This lets advanced clients create Credentials with custom context before falling back to `onChallenge` or the default flow. - -Server hooks run inline on the payment request path. Keep them short, or hand work to your logger, metrics client, or queue. `mppx` ignores thrown server hook errors so observability code doesn't block payment verification, but slow hooks still delay the response. - ## What's next This release starts with core lifecycle events. If there is an event or payload field you need, leave feedback on [GitHub](https://github.com/wevm/mppx/issues). diff --git a/src/pages/sdk/python/client.mdx b/src/pages/sdk/python/client.mdx index a7aa23b1..2c6a965b 100644 --- a/src/pages/sdk/python/client.mdx +++ b/src/pages/sdk/python/client.mdx @@ -23,7 +23,7 @@ async with Client(methods=[tempo(account=account, intents={"charge": ChargeInten ## Event handling -Register event handlers to log or trace the automatic `402` flow. Typed helpers cover common events, string names map to the lifecycle event names, and `*` receives every client event. +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 @@ -36,7 +36,7 @@ async with Client(methods=[tempo(account=account, intents={"charge": ChargeInten def log(event: str, data: dict[str, object]) -> None: print(event, data) - # Observe the selected Challenge before the client creates a Credential. + # Record the selected Challenge before the client creates a Credential. client.on_challenge_received( lambda payload: log( "payment.challenge.received", @@ -48,7 +48,7 @@ async with Client(methods=[tempo(account=account, intents={"charge": ChargeInten ) ) - # Observe the retried response after payment. + # Record the retried response after payment. client.on( PAYMENT_RESPONSE, lambda payload: log( @@ -63,7 +63,7 @@ async with Client(methods=[tempo(account=account, intents={"charge": ChargeInten 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 observe payment handling. Each registration returns an unsubscribe function. +`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 diff --git a/src/pages/sdk/python/server.mdx b/src/pages/sdk/python/server.mdx index cbd85396..512d081b 100644 --- a/src/pages/sdk/python/server.mdx +++ b/src/pages/sdk/python/server.mdx @@ -64,7 +64,7 @@ mpp = Mpp.create( ## Event handling -Register event handlers to monitor issued Challenges, successful payments, and rejected Credentials. Typed helpers cover common events, string names map to the lifecycle event names, and `*` receives every server event. +Register event handlers to record issued Challenges, successful payments, and rejected Credentials. Use helper methods for named events, event constants for shared wiring, and `*` for every server event. ```python [server.py] from mpp.events import PAYMENT_SUCCESS @@ -83,7 +83,7 @@ mpp = Mpp.create( def log(event: str, data: dict[str, object]) -> None: print(event, data) -# Observe each Challenge issued by your API. +# Record each Challenge issued by your API. mpp.on_challenge_created( lambda payload: log( "payment.challenge.created", @@ -95,7 +95,7 @@ mpp.on_challenge_created( ) ) -# Observe successful Credential verification. +# Record successful Credential verification. mpp.on( PAYMENT_SUCCESS, lambda payload: log( diff --git a/src/pages/sdk/ruby/client.mdx b/src/pages/sdk/ruby/client.mdx index 328edb30..a1dd0013 100644 --- a/src/pages/sdk/ruby/client.mdx +++ b/src/pages/sdk/ruby/client.mdx @@ -30,7 +30,7 @@ When the server returns `402`, the client: ## Event handling -Register event handlers to log or trace the automatic `402` flow. Typed helpers cover common events, event constants map to the lifecycle event names, and `*` receives every client event. +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. ```ruby [client.rb] require "json" @@ -44,7 +44,7 @@ transport = Mpp::Client::Transport.new( log = ->(event, data) { puts({ event: event, **data }.to_json) } -# Observe the selected Challenge before the client creates a Credential. +# Record the selected Challenge before the client creates a Credential. transport.on_challenge_received do |payload| log.call( "payment.challenge.received", @@ -57,7 +57,7 @@ transport.on_challenge_received do |payload| nil end -# Observe the retried response after payment. +# Record the retried response after payment. transport.on(Mpp::Events::PAYMENT_RESPONSE) do |payload| log.call("payment.response", { status: payload[:response].code }) end @@ -70,7 +70,7 @@ end response = transport.get("https://api.example.com/resource") ``` -`on_challenge_received` can return a `Credential` or `Payment` authorization string to override the default Credential creation path. Other handlers only observe payment handling. Each registration returns an unsubscribe proc. +`on_challenge_received` can return a `Credential` or `Payment` authorization string to override the default Credential creation path. Other handlers only record payment handling. Each registration returns an unsubscribe proc. ## Common methods diff --git a/src/pages/sdk/ruby/server.mdx b/src/pages/sdk/ruby/server.mdx index ce2f580d..406d3942 100644 --- a/src/pages/sdk/ruby/server.mdx +++ b/src/pages/sdk/ruby/server.mdx @@ -52,7 +52,7 @@ server = Mpp.create( ## Event handling -Register event handlers to monitor issued Challenges, successful payments, and rejected Credentials. Typed helpers cover common events, event constants map to the lifecycle event names, and `*` receives every server event. +Register event handlers to record issued Challenges, successful payments, and rejected Credentials. Use helper methods for named events, event constants for shared wiring, and `*` for every server event. ```ruby [server.rb] require "json" @@ -69,7 +69,7 @@ server = Mpp.create( log = ->(event, data) { puts({ event: event, **data }.to_json) } -# Observe each Challenge issued by your API. +# Record each Challenge issued by your API. server.on_challenge_created do |payload| log.call( "payment.challenge.created", @@ -81,7 +81,7 @@ server.on_challenge_created do |payload| ) end -# Observe successful Credential verification. +# Record successful Credential verification. server.on(Mpp::Events::PAYMENT_SUCCESS) do |payload| log.call( "payment.success", diff --git a/src/pages/sdk/rust/client.mdx b/src/pages/sdk/rust/client.mdx index e1778d2f..ffc2b8d0 100644 --- a/src/pages/sdk/rust/client.mdx +++ b/src/pages/sdk/rust/client.mdx @@ -77,7 +77,7 @@ let response = client.get("https://api.example.com/paid").send().await?; ## Event handling -Use `ClientEvents` to observe the automatic `402` flow. Typed helpers cover common events, `on()` registers a specific event kind, and `on_any()` receives every client event. +Use `ClientEvents` to record the automatic `402` flow. Use helper methods for named events, `on()` for one event kind, and `on_any()` for every client event. ```rust use mpp::client::{ @@ -88,13 +88,13 @@ use reqwest_middleware::ClientBuilder; let provider = TempoProvider::new(signer, "https://rpc.tempo.xyz")?; let events = ClientEvents::default(); -// Observe the selected Challenge before the provider creates a Credential. +// Record the selected Challenge before the provider creates a Credential. let _challenge = events.on_challenge_received(|ctx| async move { println!("challenge received: {}", ctx.challenge.id); None }); -// Observe the retried response after payment. +// Record the retried response after payment. let _response = events.on(ClientEventKind::PaymentResponse, |event| async move { if let ClientEvent::PaymentResponse(ctx) = event { println!("payment response: {}", ctx.status); @@ -111,7 +111,7 @@ let client = ClientBuilder::new(reqwest::Client::new()) .build(); ``` -`on_challenge_received` can return `Some(PaymentCredential)` to override the default provider payment flow. Other handlers only observe payment handling. Keep the returned subscription handles alive while handlers should remain registered. +`on_challenge_received` can return `Some(PaymentCredential)` to override the default provider payment flow. Other handlers only record payment handling. Keep the returned subscription handles alive while handlers remain registered. ## Multiple providers diff --git a/src/pages/sdk/rust/server.mdx b/src/pages/sdk/rust/server.mdx index 1c757b46..b2d1ba2c 100644 --- a/src/pages/sdk/rust/server.mdx +++ b/src/pages/sdk/rust/server.mdx @@ -206,7 +206,7 @@ let receipt = mpp ## Event handling -Register event handlers to observe successful Credential verification. Use the typed helper for `payment.success`, `on()` for a specific event kind, or `on_any()` to receive every server event. +Register event handlers to record successful Credential verification. Use the helper method for `payment.success`, `on()` for one event kind, and `on_any()` for every server event. ```rust use mpp::server::{Mpp, ServerEvent, ServerEventKind, TempoConfig, tempo}; @@ -215,7 +215,7 @@ let mpp = Mpp::create(tempo(TempoConfig { recipient: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", }))?; -// Observe successful Credential verification. +// Record successful Credential verification. let _success = mpp.on_payment_success(|ctx| async move { println!("payment success: {}", ctx.receipt.reference); }); @@ -232,7 +232,7 @@ let _all = mpp.on_any(|event| async move { }); ``` -Server handlers run inline with verification. Keep the returned subscription handles alive while handlers should remain registered. +Server handlers run inline with verification. Keep the returned subscription handles alive while handlers remain registered. ## Axum extractor From cb4df0d14f25a9f5b003a005744402479dff49ef Mon Sep 17 00:00:00 2001 From: Brendan Ryan <1572504+brendanjryan@users.noreply.github.com> Date: Wed, 20 May 2026 14:48:10 -0700 Subject: [PATCH 5/7] Update payment-hooks.mdx --- src/pages/blog/payment-hooks.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/blog/payment-hooks.mdx b/src/pages/blog/payment-hooks.mdx index d862ed67..44ec0e88 100644 --- a/src/pages/blog/payment-hooks.mdx +++ b/src/pages/blog/payment-hooks.mdx @@ -4,7 +4,7 @@ outline: false showAskAi: false showFeedback: false showSearch: false -description: "SDKs now expose typed payment hooks for logging, monitoring, and request context." +description: "SDKs now expose typed payment hooks for logging, monitoring, and observing request context." imageDescription: "Monitor request status with SDK hooks" --- @@ -20,7 +20,7 @@ MPP's core SDKs now expose typed payment hooks for client and server payment flo ## Why hooks matter -MPP puts the payment flow close to the request flow. That makes basic integration simple, but becomes fragile when used in production applications and existing tech stacks. +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: @@ -30,7 +30,7 @@ Hooks give you a typed place to attach that visibility: ## What changed -Client hooks observe the entire MPP payment lifecycle. Use typed helpers for common events, canonical strings for direct event names, or `*` for a single catch-all handler: +Client hooks observe the entire MPP payment lifecycle. Use typed helpers for common events, canonical strings for direct event names, or `*` for a single catch-all handler for generic logging: ```ts twoslash [client.ts] import { Mppx, tempo } from 'mppx/client' @@ -137,6 +137,6 @@ payment.onPaymentFailed(({ challenge, error, request }) => { ## What's next -This release starts with core lifecycle events. If there is an event or payload field you need, leave feedback on [GitHub](https://github.com/wevm/mppx/issues). +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). From 7fc48dcf8a675d1fd36795b895217638ee9f7537 Mon Sep 17 00:00:00 2001 From: Brendan Ryan <1572504+brendanjryan@users.noreply.github.com> Date: Wed, 20 May 2026 15:07:17 -0700 Subject: [PATCH 6/7] docs: update payment hooks release --- package.json | 4 +- pnpm-lock.yaml | 76 ++++++++++++++++---------------- src/pages/blog/index.mdx | 4 +- src/pages/blog/payment-hooks.mdx | 4 +- 4 files changed, 44 insertions(+), 44 deletions(-) diff --git a/package.json b/package.json index 04e8c2ff..51d90e20 100644 --- a/package.json +++ b/package.json @@ -25,12 +25,12 @@ "@stripe/stripe-js": "^9.3.1", "@vercel/blob": "^2.3.3", "mermaid": "^11.14.0", - "mppx": "0.6.25", + "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" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3d934396..c8ef8403 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,8 +29,8 @@ importers: specifier: ^11.14.0 version: 11.14.0 mppx: - specifier: 0.6.25 - version: 0.6.25(@modelcontextprotocol/sdk@1.27.1(@cfworker/json-schema@4.1.1)(zod@4.3.6))(express@5.2.1)(hono@4.12.18)(typescript@6.0.3)(viem@2.48.4(typescript@6.0.3)(zod@4.3.6)) + specifier: 0.6.27 + version: 0.6.27(@modelcontextprotocol/sdk@1.27.1(@cfworker/json-schema@4.1.1)(zod@4.3.6))(express@5.2.1)(hono@4.12.18)(typescript@6.0.3)(viem@2.50.4(typescript@6.0.3)(zod@4.3.6)) react: specifier: ^19 version: 19.2.5 @@ -44,14 +44,14 @@ importers: specifier: ^4.2.4 version: 4.2.4 viem: - specifier: ^2.48.4 - version: 2.48.4(typescript@6.0.3)(zod@4.3.6) + specifier: ^2.50.4 + version: 2.50.4(typescript@6.0.3)(zod@4.3.6) vocs: specifier: https://pkg.pr.new/vocs@425 version: https://pkg.pr.new/vocs@425(patch_hash=5b340733de5440b4e6ed05bb7181d1dcb81e595422ba76d685a4c14062177323)(@cfworker/json-schema@4.1.1)(@date-fns/tz@1.4.1)(@types/react@19.2.14)(date-fns@4.1.0)(mermaid@11.14.0)(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(webpack@5.105.2(esbuild@0.27.7)))(react@19.2.5)(rollup@4.60.1)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0))(waku@1.0.0-alpha.8(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(webpack@5.105.2(esbuild@0.27.7)))(react@19.2.5)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0)) wagmi: specifier: ^3.6.5 - version: 3.6.5(@tanstack/query-core@5.90.20)(@tanstack/react-query@5.90.21(react@19.2.5))(@types/react@19.2.14)(react@19.2.5)(typescript@6.0.3)(viem@2.48.4(typescript@6.0.3)(zod@4.3.6)) + version: 3.6.5(@tanstack/query-core@5.90.20)(@tanstack/react-query@5.90.21(react@19.2.5))(@types/react@19.2.14)(react@19.2.5)(typescript@6.0.3)(viem@2.50.4(typescript@6.0.3)(zod@4.3.6)) waku: specifier: ^1.0.0-alpha.8 version: 1.0.0-alpha.8(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(react-dom@19.2.5(react@19.2.5))(react-server-dom-webpack@19.2.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(webpack@5.105.2(esbuild@0.27.7)))(react@19.2.5)(terser@5.47.1)(tsx@4.21.0)(yaml@2.9.0) @@ -3022,15 +3022,15 @@ packages: mlly@1.8.2: resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} - mppx@0.6.25: - resolution: {integrity: sha512-d7+NdH9fl2usi2+I4j7zqqR2eSf3WFGCFHdzRMW7i9ikKsPwgAz2jRtlD4ZV9CHBIL16518uJuLFaMAWUn4sCQ==} + mppx@0.6.27: + resolution: {integrity: sha512-7KxM+Uau7dDcOBI9RjJYSLcDlF7glAC09eX6h64AopmQ9zZXN5gicSg7Ty8hmutXkUglEFPG/8YfWXGK4CQXSw==} hasBin: true peerDependencies: '@modelcontextprotocol/sdk': '>=1.25.0' elysia: '>=1' express: '>=5' hono: '>=4.12.18' - viem: '>=2.47.5' + viem: '>=2.50.4' peerDependenciesMeta: '@modelcontextprotocol/sdk': optional: true @@ -3116,8 +3116,8 @@ packages: outvariant@1.4.0: resolution: {integrity: sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==} - ox@0.14.20: - resolution: {integrity: sha512-rby38C3nDn8eQkf29Zgw4hkCZJ64Qqi0zRPWL8ENUQ7JVuoITqrVtwWQgM/He19SCMUEc7hS/Sjw0jIOSLJhOw==} + ox@0.14.22: + resolution: {integrity: sha512-nb5msL8qWbPglhIfZbGJAfw3cqiJjFMiWmACt7kgyWtLib12tcctbHufMT9Hb0Lr6Pt4k9I3dbpueTpbhvbqvA==} peerDependencies: typescript: '>=5.4.0' peerDependenciesMeta: @@ -3778,8 +3778,8 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - viem@2.48.4: - resolution: {integrity: sha512-mReP/rgY2P+WeeRSG4sUvccCLKfyAW1C73Y3KkobAqgzYmVna9qyUMNE44xIUkDtfvRuC33r24UhF4baBYovsg==} + viem@2.50.4: + resolution: {integrity: sha512-rf98F4s3Vlb+uJZEKfay3IbBw3CNCbVtx5Y3UIljlO2tSX420g/J0WQSYsjzBSasUFgxgsXabji14O9kGbiqgg==} peerDependencies: typescript: '>=5.0.4' peerDependenciesMeta: @@ -3994,8 +3994,8 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + ws@8.20.0: + resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -4006,8 +4006,8 @@ packages: utf-8-validate: optional: true - ws@8.20.0: - resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} + ws@8.20.1: + resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -5479,18 +5479,18 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 - '@wagmi/connectors@8.0.5(@wagmi/core@3.4.6(@tanstack/query-core@5.90.20)(@types/react@19.2.14)(react@19.2.5)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.5))(viem@2.48.4(typescript@6.0.3)(zod@4.3.6)))(typescript@6.0.3)(viem@2.48.4(typescript@6.0.3)(zod@4.3.6))': + '@wagmi/connectors@8.0.5(@wagmi/core@3.4.6(@tanstack/query-core@5.90.20)(@types/react@19.2.14)(react@19.2.5)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.5))(viem@2.50.4(typescript@6.0.3)(zod@4.3.6)))(typescript@6.0.3)(viem@2.50.4(typescript@6.0.3)(zod@4.3.6))': dependencies: - '@wagmi/core': 3.4.6(@tanstack/query-core@5.90.20)(@types/react@19.2.14)(react@19.2.5)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.5))(viem@2.48.4(typescript@6.0.3)(zod@4.3.6)) - viem: 2.48.4(typescript@6.0.3)(zod@4.3.6) + '@wagmi/core': 3.4.6(@tanstack/query-core@5.90.20)(@types/react@19.2.14)(react@19.2.5)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.5))(viem@2.50.4(typescript@6.0.3)(zod@4.3.6)) + viem: 2.50.4(typescript@6.0.3)(zod@4.3.6) optionalDependencies: typescript: 6.0.3 - '@wagmi/core@3.4.6(@tanstack/query-core@5.90.20)(@types/react@19.2.14)(react@19.2.5)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.5))(viem@2.48.4(typescript@6.0.3)(zod@4.3.6))': + '@wagmi/core@3.4.6(@tanstack/query-core@5.90.20)(@types/react@19.2.14)(react@19.2.5)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.5))(viem@2.50.4(typescript@6.0.3)(zod@4.3.6))': dependencies: eventemitter3: 5.0.1 mipd: 0.0.7(typescript@6.0.3) - viem: 2.48.4(typescript@6.0.3)(zod@4.3.6) + viem: 2.50.4(typescript@6.0.3)(zod@4.3.6) zustand: 5.0.0(@types/react@19.2.14)(react@19.2.5)(use-sync-external-store@1.4.0(react@19.2.5)) optionalDependencies: '@tanstack/query-core': 5.90.20 @@ -6510,9 +6510,9 @@ snapshots: isexe@2.0.0: {} - isows@1.0.7(ws@8.18.3): + isows@1.0.7(ws@8.20.1): dependencies: - ws: 8.18.3 + ws: 8.20.1 jest-worker@27.5.1: dependencies: @@ -7226,11 +7226,11 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.3 - mppx@0.6.25(@modelcontextprotocol/sdk@1.27.1(@cfworker/json-schema@4.1.1)(zod@4.3.6))(express@5.2.1)(hono@4.12.18)(typescript@6.0.3)(viem@2.48.4(typescript@6.0.3)(zod@4.3.6)): + mppx@0.6.27(@modelcontextprotocol/sdk@1.27.1(@cfworker/json-schema@4.1.1)(zod@4.3.6))(express@5.2.1)(hono@4.12.18)(typescript@6.0.3)(viem@2.50.4(typescript@6.0.3)(zod@4.3.6)): dependencies: incur: 0.4.5 - ox: 0.14.20(typescript@6.0.3)(zod@4.4.3) - viem: 2.48.4(typescript@6.0.3)(zod@4.3.6) + ox: 0.14.22(typescript@6.0.3)(zod@4.4.3) + viem: 2.50.4(typescript@6.0.3)(zod@4.3.6) zod: 4.4.3 optionalDependencies: '@modelcontextprotocol/sdk': 1.27.1(@cfworker/json-schema@4.1.1)(zod@4.3.6) @@ -7291,7 +7291,7 @@ snapshots: outvariant@1.4.0: {} - ox@0.14.20(typescript@6.0.3)(zod@4.3.6): + ox@0.14.22(typescript@6.0.3)(zod@4.3.6): dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 @@ -7306,7 +7306,7 @@ snapshots: transitivePeerDependencies: - zod - ox@0.14.20(typescript@6.0.3)(zod@4.4.3): + ox@0.14.22(typescript@6.0.3)(zod@4.4.3): dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 @@ -8067,16 +8067,16 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - viem@2.48.4(typescript@6.0.3)(zod@4.3.6): + viem@2.50.4(typescript@6.0.3)(zod@4.3.6): dependencies: '@noble/curves': 1.9.1 '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 abitype: 1.2.3(typescript@6.0.3)(zod@4.3.6) - isows: 1.0.7(ws@8.18.3) - ox: 0.14.20(typescript@6.0.3)(zod@4.3.6) - ws: 8.18.3 + isows: 1.0.7(ws@8.20.1) + ox: 0.14.22(typescript@6.0.3)(zod@4.3.6) + ws: 8.20.1 optionalDependencies: typescript: 6.0.3 transitivePeerDependencies: @@ -8262,14 +8262,14 @@ snapshots: w3c-keyname@2.2.8: {} - wagmi@3.6.5(@tanstack/query-core@5.90.20)(@tanstack/react-query@5.90.21(react@19.2.5))(@types/react@19.2.14)(react@19.2.5)(typescript@6.0.3)(viem@2.48.4(typescript@6.0.3)(zod@4.3.6)): + wagmi@3.6.5(@tanstack/query-core@5.90.20)(@tanstack/react-query@5.90.21(react@19.2.5))(@types/react@19.2.14)(react@19.2.5)(typescript@6.0.3)(viem@2.50.4(typescript@6.0.3)(zod@4.3.6)): dependencies: '@tanstack/react-query': 5.90.21(react@19.2.5) - '@wagmi/connectors': 8.0.5(@wagmi/core@3.4.6(@tanstack/query-core@5.90.20)(@types/react@19.2.14)(react@19.2.5)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.5))(viem@2.48.4(typescript@6.0.3)(zod@4.3.6)))(typescript@6.0.3)(viem@2.48.4(typescript@6.0.3)(zod@4.3.6)) - '@wagmi/core': 3.4.6(@tanstack/query-core@5.90.20)(@types/react@19.2.14)(react@19.2.5)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.5))(viem@2.48.4(typescript@6.0.3)(zod@4.3.6)) + '@wagmi/connectors': 8.0.5(@wagmi/core@3.4.6(@tanstack/query-core@5.90.20)(@types/react@19.2.14)(react@19.2.5)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.5))(viem@2.50.4(typescript@6.0.3)(zod@4.3.6)))(typescript@6.0.3)(viem@2.50.4(typescript@6.0.3)(zod@4.3.6)) + '@wagmi/core': 3.4.6(@tanstack/query-core@5.90.20)(@types/react@19.2.14)(react@19.2.5)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.5))(viem@2.50.4(typescript@6.0.3)(zod@4.3.6)) react: 19.2.5 use-sync-external-store: 1.4.0(react@19.2.5) - viem: 2.48.4(typescript@6.0.3)(zod@4.3.6) + viem: 2.50.4(typescript@6.0.3)(zod@4.3.6) optionalDependencies: typescript: 6.0.3 transitivePeerDependencies: @@ -8378,10 +8378,10 @@ snapshots: wrappy@1.0.2: {} - ws@8.18.3: {} - ws@8.20.0: {} + ws@8.20.1: {} + yallist@3.1.1: {} yaml@2.9.0: {} diff --git a/src/pages/blog/index.mdx b/src/pages/blog/index.mdx index 7e480c9d..b7fe1110 100644 --- a/src/pages/blog/index.mdx +++ b/src/pages/blog/index.mdx @@ -16,9 +16,9 @@ import { BlogPostList } from "../../components/BlogPostList"; SDKs now expose typed payment hooks for logging, monitoring, and request context., + description: <>Core SDKs now expose typed payment hooks for logging, monitoring, and request context., to: "/blog/payment-hooks", }, { diff --git a/src/pages/blog/payment-hooks.mdx b/src/pages/blog/payment-hooks.mdx index 44ec0e88..d783aecb 100644 --- a/src/pages/blog/payment-hooks.mdx +++ b/src/pages/blog/payment-hooks.mdx @@ -4,7 +4,7 @@ outline: false showAskAi: false showFeedback: false showSearch: false -description: "SDKs now expose typed payment hooks for logging, monitoring, and observing request context." +description: "Core SDKs now expose typed payment hooks for logging, monitoring, and request context." imageDescription: "Monitor request status with SDK hooks" --- @@ -12,7 +12,7 @@ imageDescription: "Monitor request status with SDK hooks" Blog -

Wednesday, May 20, 2026

+

Thursday, May 21, 2026

# Payment hooks [Observe MPP requests with typed lifecycle events] From 5b030af6cb10387c6be565d8808a8b0379409aac Mon Sep 17 00:00:00 2001 From: Brendan Ryan <1572504+brendanjryan@users.noreply.github.com> Date: Wed, 20 May 2026 15:44:23 -0700 Subject: [PATCH 7/7] docs: simplify payment hooks example --- src/pages/blog/payment-hooks.mdx | 77 ++------------------------------ 1 file changed, 4 insertions(+), 73 deletions(-) diff --git a/src/pages/blog/payment-hooks.mdx b/src/pages/blog/payment-hooks.mdx index d783aecb..1b830e38 100644 --- a/src/pages/blog/payment-hooks.mdx +++ b/src/pages/blog/payment-hooks.mdx @@ -30,61 +30,7 @@ Hooks give you a typed place to attach that visibility: ## What changed -Client hooks observe the entire MPP payment lifecycle. Use typed helpers for common events, canonical strings for direct event names, or `*` for a single catch-all handler for generic logging: - -```ts twoslash [client.ts] -import { Mppx, tempo } from 'mppx/client' -import { privateKeyToAccount } from 'viem/accounts' - -const account = privateKeyToAccount(process.env.MPP_PRIVATE_KEY as `0x${string}`) - -const mppx = Mppx.create({ - methods: [tempo({ account })], - polyfill: false, -}) - -const log = (event: string, data: Record) => { - console.log(event, data) -} - -mppx.onChallengeReceived(({ challenge }) => { - // Observe the selected Challenge before the SDK creates a credential. - log('payment.challenge.received', { - challengeId: challenge.id, - intent: challenge.intent, - method: challenge.method, - }) - return undefined -}) - -mppx.on('payment.response', ({ challenge, response }) => { - // Use canonical event names when you want to share event wiring. - log('payment.response', { - challengeId: challenge.id, - status: response.status, - }) -}) - -mppx.on('*', ({ name, payload }) => { - // Use `*` to send all payment events through one telemetry path. - log('payment.event', { - hasChallenge: 'challenge' in payload, - name, - }) -}) - -mppx.onPaymentFailed(({ error, input }) => { - // Capture failures from challenge parsing, credential creation, or retry handling. - log('payment.failed', { - error: error instanceof Error ? error.message : String(error), - input: String(input), - }) -}) - -const response = await mppx.fetch('https://mpp.dev/api/ping/paid') -``` - -Server hooks observe issued Challenges, successful payments, and rejected Credentials: +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' @@ -97,36 +43,21 @@ const log = (event: string, data: Record) => { console.log(event, data) } -payment.onChallengeCreated(({ challenge, request }) => { - // Observe each `402` Challenge before it is returned to the client. +payment.onChallengeCreated(({ challenge, request }) => { // [!code hl] log('payment.challenge.created', { amount: request.amount, challengeId: challenge.id, - currency: request.currency, }) }) -payment.on('payment.success', ({ receipt, request }) => { - // Use canonical event names when you want to share event wiring. +payment.on('payment.success', ({ receipt, request }) => { // [!code hl] log('payment.success', { amount: request.amount, - currency: request.currency, reference: receipt.reference, - status: receipt.status, - }) -}) - -payment.on('*', ({ name, payload }) => { - // Use `*` to send all payment events through one telemetry path. - log('payment.event', { - challengeId: payload.challenge.id, - intent: payload.method.intent, - name, }) }) -payment.onPaymentFailed(({ challenge, error, request }) => { - // Record failed Credential verification with request and Challenge context. +payment.onPaymentFailed(({ challenge, error, request }) => { // [!code hl] log('payment.failed', { amount: request.amount, challengeId: challenge.id,