> ## Documentation Index
> Fetch the complete documentation index at: https://laas.mippo.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks for LaaS transaction and KYC events

> Receive real-time notifications for KYC status changes, transaction lifecycle events, and settlement callbacks with signed webhook payloads.

Mippo sends signed `POST` requests to your webhook URL when key events occur. Always verify the signature before processing.

## Events

| Event                   | When                                     |
| ----------------------- | ---------------------------------------- |
| `transaction.completed` | Stablecoin settled on-chain              |
| `transaction.failed`    | Payment expired or on-chain failure      |
| `transaction.pix_ready` | Pix QR ready after liveness verification |
| `kyc.approved`          | KYC submission approved                  |
| `kyc.rejected`          | KYC submission rejected                  |

## Payload structure

```json theme={null}
{
  "event": "transaction.completed",
  "timestamp": "2026-07-01T18:12:44.000Z",
  "data": {
    "transactionId": "tx_1719000000_xyz",
    "txHash": "0xabc123...",
    "asset": "USDT",
    "amount": "90.25",
    "walletAddress": "0xYourUserWalletAddress"
  }
}
```

## Signature verification

Every request includes an `X-Mippo-Signature` header — an HMAC-SHA256 of the raw request body using your webhook secret.

<Warning>
  Always verify the signature. Never process webhook events from unverified sources.
</Warning>

```typescript webhook-handler.ts theme={null}
import crypto from 'crypto';

function verifyWebhook(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(expected, 'hex'),
    Buffer.from(signature, 'hex')
  );
}

// Express example
app.post('/webhooks/mippo', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-mippo-signature'] as string;

  if (!verifyWebhook(req.body.toString(), sig, process.env.WEBHOOK_SECRET!)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const { event, data } = JSON.parse(req.body.toString());

  switch (event) {
    case 'transaction.completed':
      // Credit user account, send confirmation email, etc.
      console.log('Settled:', data.txHash);
      break;
    case 'kyc.rejected':
      // Notify user their KYC failed
      break;
  }

  res.sendStatus(200); // Acknowledge within 5 seconds
});
```

<Note>
  Use `express.raw()` (not `express.json()`) so the raw body is preserved for signature verification. JSON parsing after verification is fine.
</Note>

## Retry policy

If your endpoint returns a non-`2xx` status or times out (5s), Mippo retries with exponential backoff:

| Attempt   | Delay           |
| --------- | --------------- |
| 1st retry | 1 minute        |
| 2nd retry | 5 minutes       |
| 3rd retry | 30 minutes      |
| 4th retry | 2 hours         |
| 5th retry | 6 hours — final |

After 5 failed retries, the event is marked as `FAILED` and you are notified by email.

## Rotating your webhook secret

In the [Partner Dashboard](https://partners.mippo.io/partner/webhooks), click **Rotate secret**. Your old secret remains valid for **30 minutes** during the transition window.
