> ## 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 transaction and KYC events

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

Supercosmos 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.

```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.

## Retry policy

If your endpoint returns a non-`2xx` status or times out (5s), Supercosmos 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 [developer dashboard](https://partners.mippo.io/partner/webhooks), click **Rotate secret**. Your old secret remains valid for **30 minutes** during the transition window.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.