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

# Webhooks

> Receive real-time payment notifications from My Crypto Server

Webhooks let My Crypto Server notify your server when events happen — like when a payment is confirmed. You register an HTTPS endpoint, subscribe to event types, and we POST a signed JSON payload to your URL.

## Event types

| Event               | When it fires                                                         |
| ------------------- | --------------------------------------------------------------------- |
| `session.paid`      | Payment confirmed with the required number of confirmations           |
| `session.paid_late` | Payment confirmed after the session expired (within the grace window) |
| `session.expired`   | Session timed out with no payment detected                            |
| `session.underpaid` | A transfer was detected but the amount is less than required          |
| `session.overpaid`  | A transfer was detected for more than the required amount             |
| `session.detected`  | First transfer detected (before finality confirmation)                |

## Registering an endpoint

Create a webhook endpoint via the API or the dashboard.

```bash theme={null}
curl -X POST https://your-domain.com/api/v1/webhook_endpoints \
  -H "Authorization: Bearer ck_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: my-webhook-setup-v1" \
  -d '{
    "url": "https://your-server.com/webhooks/crypto",
    "events": ["session.paid", "session.expired"]
  }'
```

The response includes a `secret` field — **copy it immediately**, it is never shown again. You use this to verify incoming webhook signatures.

```json theme={null}
{
  "id": "wh_...",
  "object": "webhook_endpoint",
  "url": "https://your-server.com/webhooks/crypto",
  "events": ["session.paid", "session.expired"],
  "active": true,
  "secret": "whsec_...",
  "secretPrefix": "whsec_ab",
  "createdAt": "2024-01-01T00:00:00Z"
}
```

## Webhook payload

Each delivery is a POST with a JSON body containing an `Event` object:

```json theme={null}
{
  "id": "evt_01HQXYZ...",
  "object": "event",
  "type": "session.paid",
  "livemode": true,
  "data": {
    "id": "cs_01HQABC...",
    "status": "paid",
    "txHash": "0xabc123...",
    "amount": { "wei": "50000000000000000", "eth": "0.05" },
    "fiat": { "amount": "100.00", "currency": "USD" }
  },
  "createdAt": "2024-01-15T10:30:00Z"
}
```

## Verifying signatures

Every delivery includes two headers:

```
X-Webhook-Signature: t=1705312200,v1=abc123...
X-Webhook-Timestamp: 1705312200
```

The signed string is `${timestamp}.${rawBody}` — HMAC-SHA256 with your endpoint's secret. **Always verify the signature before processing a webhook.**

### Using the SDK

```ts theme={null}
import { verifyWebhook } from "@your-org/checkout-sdk/webhooks";

// Express example
app.post("/webhooks/crypto", express.raw({ type: "application/json" }), (req, res) => {
  let event;
  try {
    event = verifyWebhook({
      payload: req.body.toString(),
      signature: req.headers["x-webhook-signature"],
      timestamp: req.headers["x-webhook-timestamp"],
      secret: process.env.CHECKOUT_WEBHOOK_SECRET!,
    });
  } catch (err) {
    return res.status(400).send("Invalid signature");
  }

  if (event.type === "session.paid") {
    const session = event.data;
    // Fulfill the order for session.metadata.orderId
    await fulfillOrder(session.metadata.orderId, session.txHash);
  }

  res.json({ received: true });
});
```

### Manual verification (Node.js)

```js theme={null}
const crypto = require("node:crypto");

const rawBody = req.body.toString();
const sigHeader = req.headers["x-webhook-signature"];
const [tPart, v1Part] = sigHeader.split(",");
const ts = tPart.split("=")[1];
const v1 = v1Part.split("=")[1];

// Reject stale webhooks (>5 min)
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) {
  throw new Error("Webhook timestamp outside tolerance window");
}

const expected = crypto
  .createHmac("sha256", process.env.CHECKOUT_WEBHOOK_SECRET)
  .update(`${ts}.${rawBody}`)
  .digest("hex");

if (!crypto.timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(v1, "hex"))) {
  throw new Error("Webhook signature mismatch");
}

const event = JSON.parse(rawBody);
```

## Delivery and retries

My Crypto Server expects a `2xx` response within 30 seconds. If your server doesn't respond or returns a non-2xx status, delivery is retried with exponential backoff — up to 6 attempts over approximately 31 hours.

You can replay any delivery from the dashboard under **Webhooks → Deliveries**.

## Testing

Use the test endpoint to fire a synthetic `session.paid` event to your registered URL without needing an actual payment:

```bash theme={null}
curl -X POST https://your-domain.com/api/v1/webhook_endpoints/{id}/test \
  -H "Authorization: Bearer ck_live_..."
```
