402.md

Webhooks

Receive real-time notifications for payment and order events

Headers

Every webhook request includes:

HeaderDescription
X-Webhook-EventEvent type (e.g. checkout.completed)
X-Webhook-IdUnique delivery ID for idempotency
Content-Typeapplication/json

Delivery

  • Attempts: 5 retries on failure
  • Backoff: exponential with 5-second base (5s, 10s, 20s, 40s, 80s)
  • Timeout: 10 seconds per attempt
  • Method: POST

Your endpoint must return a 2xx status code to acknowledge receipt. Any other status triggers a retry.

Events

checkout.completed

Fired when a checkout session payment is confirmed on-chain.

{
  "event": "checkout.completed",
  "sessionId": "sess_abc123",
  "transactionId": "tx_def456",
  "txHash": "0x8f3d...a2c1",
  "amount": "49.00",
  "skillId": "skill_xyz",
  "type": "subscription",
  "metadata": { "planId": "premium-30d" }
}

payment.confirmed

Fired when an x402 API payment is verified.

{
  "event": "payment.confirmed",
  "transactionId": "tx_def456",
  "txHash": "0x8f3d...a2c1",
  "amount": "0.05",
  "skillId": "skill_xyz",
  "walletAddress": "0x742d...5f0b"
}

subscription.created

Fired when a new subscription is created.

{
  "event": "subscription.created",
  "transactionId": "tx_def456",
  "txHash": "0x8f3d...a2c1",
  "skillId": "skill_xyz",
  "amount": "49.00",
  "durationDays": 30,
  "expiresAt": "2026-03-23T00:00:00Z"
}

subscription.expired

Fired when a subscription reaches its expiration date.

{
  "event": "subscription.expired",
  "subscriptionId": "sub_abc123",
  "skillId": "skill_xyz",
  "expiredAt": "2026-03-23T00:00:00Z"
}

refund.completed

Fired when a refund is processed and USDC is returned to the buyer.

{
  "event": "refund.completed",
  "transactionId": "tx_abc123",
  "refundTxHash": "0x1a2b...3c4d",
  "amount": "49.00"
}

refund.requested

Fired when a buyer requests a refund (pending merchant approval).

{
  "event": "refund.requested",
  "transactionId": "tx_abc123",
  "reason": "Service not as described"
}

order.created

Fired when a product order is created.

{
  "event": "order.created",
  "orderId": "ord_abc123",
  "transactionId": "tx_def456",
  "txHash": "0x8f3d...a2c1",
  "skillId": "skill_xyz",
  "amount": "99.99",
  "quantity": 1,
  "requiresShipping": true
}

Idempotency

Use the X-Webhook-Id header to deduplicate deliveries. Store processed webhook IDs and skip any you've already handled:

app.post('/webhooks/402md', (req, res) => {
  const webhookId = req.headers['x-webhook-id']

  if (await isProcessed(webhookId)) {
    return res.sendStatus(200)
  }

  const event = req.body

  switch (event.event) {
    case 'checkout.completed':
      await handleCheckout(event)
      break
    case 'refund.completed':
      await handleRefund(event)
      break
  }

  await markProcessed(webhookId)
  res.sendStatus(200)
})

Dashboard

View webhook delivery logs and retry failed deliveries from the 402.md dashboard. Each delivery shows the request payload, response status, and timing.