Skip to content
LogoLogo

Payment hooks

Observe payment lifecycles

Payment hooks let you attach logging, metrics, tracing, and request-local context to MPP payment flows without rewriting the payment handler.

Lifecycle overview

Payment hooks follow the 402 payment flow. The server issues a Challenge for an unpaid request. The client selects that Challenge, creates a Credential, and retries the request. The server verifies the Credential, returns the protected response, and attaches a Receipt.

Server hooks

Register server hooks on the object returned by Mppx.create from mppx/server.

HookCanonical eventRuns when
onChallengeCreatedchallenge.createdThe server issues a payment Challenge
onPaymentSuccesspayment.successThe server verifies payment and creates a Receipt
onPaymentFailedpayment.failedA submitted Credential or standalone verification fails
on('*', handler)*Any server payment event fires

Server handlers are awaited inline and sequentially on the payment request path. Handler errors are ignored and do not change payment handling, but slow handlers still delay the response.

server.ts
import { ,  } from 'mppx/server'
 
const  = .({
  : [.(), .()],
})
 
.(({ , ,  }) => { 
  .('challenge.created', {
    : .,
    : .,
    : .,
    : .,
  })
})
 
.(({ , ,  }) => { 
  .('payment.success', {
    : .,
    : .,
    : .,
    : .,
  })
})
 
.(({ , ,  }) => { 
  .('payment.failed', {
    : ?.,
    : .,
    : .,
    : .,
  })
})

Client hooks

Register client hooks on the object returned by Mppx.create from mppx/client.

HookCanonical eventRuns when
onChallengeReceivedchallenge.receivedA 402 Challenge is selected
onCredentialCreatedcredential.createdA Credential is created for the selected Challenge
onPaymentResponsepayment.responseThe retry after payment returns a successful response
onPaymentFailedpayment.failedChallenge 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 client hooks are observers: thrown errors are ignored and do not change payment handling.

client.ts
import { ,  } from 'mppx/client'
import {  } from 'viem/accounts'
 
const  = ('0x...')
 
const  = .({
  : [
    .({  }),
    .({ , : '10' }),
  ],
  : false,
})
 
.(({  }) => { 
  .('challenge.received', {
    : .,
    : .,
    : .,
  })
})
 
.(({  }) => { 
  .('credential.created', {
    : .,
    : .,
  })
})
 
.(({ ,  }) => { 
  .('payment.response', {
    : .,
    : .,
  })
})
 
.(({ ,  }) => { 
  .('payment.failed', {
    : ?.,
    :  instanceof  ? . : 'Error',
  })
})

Charge intent

For charge, one request maps to one payment. The hook events describe the Challenge, the client Credential, and the server Receipt for that charge.

Use method.intent === 'charge' on server hooks or challenge.intent === 'charge' on client hooks to isolate charge telemetry.

Session intent

For session, hooks observe the MPP request flow around session Challenges and Credentials. Session-specific channel open, voucher, top-up, close, and settlement behavior is handled by the session method APIs; the payment hook names remain the same.

Use method.intent === 'session' on server hooks or challenge.intent === 'session' on client hooks to isolate session telemetry.

server.ts
import { ,  } from 'mppx/server'
 
const  = .({
  : [.()],
})
 
.(({ , ,  }) => { 
  if (. !== 'session') return
 
  .('session.payment.success', {
    : .,
    : .,
    : .,
  })
})

Subscription intent

For subscription, hooks observe the payment flow when the server issues a subscription Challenge and the client returns a subscription Credential. Later requests can be authorized by method-specific subscription state.

Use method.intent === 'subscription' on server hooks or challenge.intent === 'subscription' on client hooks to isolate subscription telemetry.

Event payloads

Payloads carry the selected method, Challenge, request context, and event result. on('*') receives { name, payload }; typed helpers receive the inner payload directly.

server-event.ts
{
  name: 'payment.success',
  payload: {
    challenge: { id: 'ch_123', intent: 'session', method: 'tempo' },
    method: { intent: 'session', name: 'tempo' },
    receipt: {
      method: 'tempo',
      reference: '0x...',
      status: 'success',
      timestamp: '2026-06-24T00:00:00.000Z',
    },
    request: { amount: '0.01' },
  },
}
client-event.ts
{
  name: 'payment.response',
  payload: {
    challenge: { id: 'ch_123', intent: 'session', method: 'tempo' },
    credential: 'Payment ...',
    method: { intent: 'session', name: 'tempo' },
    response: new Response(null, { status: 200 }),
  },
}

Subscription management

Each hook registration returns an unsubscribe function. Keep the function when a handler is temporary, such as request-scoped instrumentation, tests, or a process that recreates payment instances. Call it to detach the handler and stop receiving events.

server.ts
import { ,  } from 'mppx/server'
 
const  = .({
  : [.()],
})
 
const  = .(({  }) => {
  .(.)
})
 
()