Skip to content
LogoLogo

Identity

Verify clients without payment

Every MPP Credential carries a cryptographic identity—the client's public key in the source field. Your server gets a verified identity on every request, whether the client pays or not. This page covers how to use that identity for access control, gating, and multi-step workflows.

Overview

The Credential already proves the client controls a specific public key. The source field is the same regardless of the payment amount, which means you can use the MPP flow purely for identity when payment isn't needed.

Extract the client's identity from any verified request using Credential.fromRequest:

server.ts
import {  } from 'mppx'
 
const  = .()
const  = .
"did:pkh:eip155:4217:0x1234..."

Backends key workloads, sessions, and access control on this public key. The payment flow is orthogonal to identity, and can be used in combination with other methods.

Methods

MethodPayment methodsDescription
Zero-dollar authTempo, Lightning, Solana, CustomProves key ownership with no funds transfer

Zero-dollar auth

Zero-dollar auth uses the standard Challenge → Credential flow with the amount set to 0. The client signs the Challenge to prove key ownership. No funds move on-chain, and no additional protocol extensions are required.

For Tempo charge, zero-dollar auth now uses a proof Credential payload instead of a real transaction. The client signs a proof message over the Challenge ID, and the server verifies that signature against the source DID.

The Credential contains the client's public key and a valid signature, giving the server a verified identity to associate with the request.

For Tempo, the server rejects transaction and hash payloads for zero-amount Challenges and requires proof.

Case study: long-running jobs

A service accepts a paid request to start work, then lets the client poll for results using zero-dollar auth. The server keys workloads on the client's public key.

Client submits a job (paid)

The client sends a request with payment to create a new job. The Credential includes both payment proof and the client's public key.

client.ts
const  = await ('https://api.example.com/v1/jobs')
const {  } = await .()
{ jobId: "abc123" }

The server records the job and associates it with the client's public key from the Credential.

Server stores the public key

After the payment middleware verifies the Credential, extract the client's identity from the request:

server.ts
import { Credential } from 'mppx'
 
export async function handler(request: Request) {
  const result = await mppx.charge({ amount: '1.00' })(request)
  if (result.status === 402) return result.challenge
 
  const credential = Credential.fromRequest(request)
  const pubkey = credential.source
  const jobId = createJob({ owner: pubkey })
 
  return result.withReceipt(Response.json({ jobId }))
}

Client polls for status (zero-dollar auth)

The client polls the job endpoint. The server issues a zero-dollar Challenge—the client signs it to prove they own the same key.

server.ts
export async function statusHandler(request: Request) {
  const result = await mppx.charge({ amount: '0' })(request) 
  if (result.status === 402) return result.challenge
 
  const credential = Credential.fromRequest(request)
  const job = getJob(jobIdFromUrl(request))
 
  if (job.owner !== credential.source) {
    return Response.json({ error: 'Not your job' }, { status: 403 })
  }
 
  return result.withReceipt(Response.json({ result: job.result, status: job.status }))
}

Case study: paid unlock with free access

A service charges once to unlock a resource, then grants repeated free access tied to the client's identity. This replaces API keys with cryptographic ownership.

Client pays to unlock

The client pays once to gain access. The server records the public key as an authorized user.

server.ts
export async function unlockHandler(request: Request) {
  const result = await mppx.charge({ amount: '50.00' })(request)
  if (result.status === 402) return result.challenge
 
  const credential = Credential.fromRequest(request)
  grantAccess({ dataset: 'premium', owner: credential.source }) 
 
  return result.withReceipt(Response.json({ status: 'unlocked' }))
}

Client accesses the resource (zero-dollar auth)

Subsequent requests use zero-dollar auth. The server checks the client's identity against the access list.

server.ts
export async function accessHandler(request: Request) {
  const result = await mppx.charge({ amount: '0' })(request) 
  if (result.status === 402) return result.challenge
 
  const credential = Credential.fromRequest(request)
  if (!hasAccess({ dataset: 'premium', owner: credential.source })) {
    return Response.json({ error: 'Not unlocked' }, { status: 403 })
  }
 
  return result.withReceipt(Response.json({ data: getDataset('premium') }))
}

Case study: multi-step agent workflow

An agent orchestrates a pipeline where one paid step kicks off several follow-up steps that only need identity. Each step verifies the same public key to maintain continuity across the workflow.

Agent starts the pipeline (paid)

The agent pays to kick off generation. The server returns a pipeline ID tied to the agent's public key.

server.ts
export async function createPipelineHandler(request: Request) {
  const result = await mppx.charge({ amount: '5.00' })(request)
  if (result.status === 402) return result.challenge
 
  const credential = Credential.fromRequest(request)
  const pipelineId = createPipeline({ owner: credential.source }) 
 
  return result.withReceipt(Response.json({ pipelineId }))
}

Agent retrieves intermediate results (zero-dollar auth)

The agent polls each stage of the pipeline. Every request proves the same identity without additional payment.

server.ts
export async function stageHandler(request: Request) {
  const result = await mppx.charge({ amount: '0' })(request) 
  if (result.status === 402) return result.challenge
 
  const credential = Credential.fromRequest(request)
  const pipeline = getPipeline(pipelineIdFromUrl(request))
 
  if (pipeline.owner !== credential.source) {
    return Response.json({ error: 'Not your pipeline' }, { status: 403 })
  }
 
  const stage = pipeline.stages[stageFromUrl(request)]
  return result.withReceipt(Response.json({ output: stage.output, status: stage.status }))
}

Agent downloads the final artifact (zero-dollar auth)

The final download also uses zero-dollar auth—the server already collected payment at the start.

server.ts
export async function resultHandler(request: Request) {
  const result = await mppx.charge({ amount: '0' })(request) 
  if (result.status === 402) return result.challenge
 
  const credential = Credential.fromRequest(request)
  const pipeline = getPipeline(pipelineIdFromUrl(request))
 
  if (pipeline.owner !== credential.source) {
    return Response.json({ error: 'Not your pipeline' }, { status: 403 })
  }
 
  return result.withReceipt(Response.json({ result: pipeline.finalResult }))
}