Wednesday, May 20, 2026
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. 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
Challenges, successful payments, failedCredentials, and paid retry responses, then attachChallengeIDs, method names, intents, amounts, currencies, andReceiptreferences 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 the entire MPP payment lifecycle. Use typed helpers for common events, canonical strings for direct event names, or * for a single catch-all handler:
import { , } from 'mppx/client'
import { } from 'viem/accounts'
const = (.. as `0x${string}`)
const = .({
: [({ })],
: false,
})
const = (: string, : <string, unknown>) => {
.(, )
}
.(({ }) => {
// Observe the selected Challenge before the SDK creates a credential.
('payment.challenge.received', {
: .,
: .,
: .,
})
return
})
.('payment.response', ({ , }) => {
// Use canonical event names when you want to share event wiring.
('payment.response', {
: .,
: .,
})
})
.('*', ({ , }) => {
// Use `*` to send all payment events through one telemetry path.
('payment.event', {
: 'challenge' in ,
,
})
})
.(({ , }) => {
// Capture failures from challenge parsing, credential creation, or retry handling.
('payment.failed', {
: instanceof ? . : (),
: (),
})
})
const = await .('https://mpp.dev/api/ping/paid')Server hooks observe issued Challenges, successful payments, and rejected Credentials:
import { , } from 'mppx/server'
const = .({
: [.()],
})
const = (: string, : <string, unknown>) => {
.(, )
}
.(({ , }) => {
// Observe each `402` Challenge before it is returned to the client.
('payment.challenge.created', {
: .,
: .,
: .,
})
})
.('payment.success', ({ , }) => {
// Use canonical event names when you want to share event wiring.
('payment.success', {
: .,
: .,
: .,
: .,
})
})
.('*', ({ , }) => {
// Use `*` to send all payment events through one telemetry path.
('payment.event', {
: ..,
: ..,
,
})
})
.(({ , , }) => {
// Record failed Credential verification with request and Challenge context.
('payment.failed', {
: .,
: .,
: .,
})
})What's next
This release starts with core lifecycle events. If there is an event or payload field you need, leave feedback on GitHub.