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

# Webhooks

> Receive real-time notifications when payment events occur

Webhooks let your application receive real-time HTTP notifications when events happen in your moneydevkit account — like a checkout completing or a subscription renewing. Instead of polling the API, you register an endpoint URL and moneydevkit pushes events to you as they occur.

## Event Types

| Event                   | Description                                                     |
| ----------------------- | --------------------------------------------------------------- |
| `checkout.completed`    | A checkout session was paid successfully                        |
| `subscription.created`  | A new subscription was created after first payment              |
| `subscription.renewed`  | An existing subscription was renewed for another period         |
| `subscription.canceled` | A subscription was canceled (by customer or after grace period) |

## Setup

<Steps>
  <Step title="Create a webhook endpoint">
    In your [dashboard](https://moneydevkit.com/dashboard), navigate to **Apps → your app → Webhooks → Add Endpoint**.

    Enter the URL where you want to receive events (e.g. `https://yourapp.com/api/webhooks/mdk`), then select the event types you want to subscribe to.
  </Step>

  <Step title="Store the signing secret">
    After creating the endpoint, copy the **signing secret** (starts with `whsec_`). Store it in your environment variables and install the `standardwebhooks` package for signature verification:

    ```env theme={null}
    MDK_WEBHOOK_SECRET=whsec_your_secret_here
    ```

    ```bash theme={null}
    npm install standardwebhooks
    ```

    <Warning>
      Keep your signing secret private. Never commit it to source control or expose it in client-side code.
    </Warning>
  </Step>

  <Step title="Create a webhook handler">
    Add an endpoint in your app to receive and verify webhook events. Here's an example using Next.js App Router:

    ```ts theme={null}
    // app/api/webhooks/mdk/route.ts
    import { NextRequest, NextResponse } from "next/server"
    import { Webhook } from "standardwebhooks"

    const secret = process.env.MDK_WEBHOOK_SECRET!

    export async function POST(req: NextRequest) {
      const body = await req.text()
      const headers = {
        "webhook-id": req.headers.get("webhook-id") ?? "",
        "webhook-timestamp": req.headers.get("webhook-timestamp") ?? "",
        "webhook-signature": req.headers.get("webhook-signature") ?? "",
      }

      // Verify the signature
      const wh = new Webhook(secret)
      let payload: Record<string, unknown>
      try {
        payload = wh.verify(body, headers) as Record<string, unknown>
      } catch {
        return NextResponse.json({ error: "Invalid signature" }, { status: 401 })
      }

      // Event-specific fields are in payload.data
      const { type, data } = payload as { type: string, data: Record<string, unknown> }

      switch (type) {
        case "checkout.completed":
          // Fulfill the order — data.amountSats, data.customer, etc.
          break
        case "subscription.created":
          // Activate the subscription — data.customerId, data.productId, etc.
          break
        case "subscription.renewed":
          // Extend access for the new period
          break
        case "subscription.canceled":
          // Revoke access or schedule revocation
          break
      }

      return NextResponse.json({ received: true })
    }
    ```
  </Step>
</Steps>

## Event Payloads

Every webhook payload is wrapped in a standard envelope with `type`, `timestamp`, `id`, and a `data` object containing the event-specific fields.

### checkout.completed

Sent when a checkout session is paid successfully.

```json theme={null}
{
  "type": "checkout.completed",
  "timestamp": "2025-07-01T12:00:00.000Z",
  "id": "evt_abc123",
  "data": {
    "status": "COMPLETED",
    "amountSats": 10000,
    "currency": "SAT",
    "netAmount": 9900,
    "metadata": {},
    "customer": {
      "email": "alice@example.com",
      "name": "Alice"
    },
    "product": {
      "id": "prod_xyz",
      "name": "Premium Plan"
    }
  }
}
```

### subscription.created

Sent when a customer's first subscription payment is confirmed.

```json theme={null}
{
  "type": "subscription.created",
  "timestamp": "2025-07-01T12:00:00.000Z",
  "id": "evt_def456",
  "data": {
    "status": "active",
    "customerId": "cus_abc",
    "productId": "prod_xyz",
    "amount": 10000,
    "currency": "SAT",
    "recurringInterval": "MONTH",
    "currentPeriodEnd": "2025-08-01T00:00:00.000Z",
    "customer": {
      "email": "alice@example.com",
      "name": "Alice"
    }
  }
}
```

### subscription.renewed

Sent when a subscription is successfully renewed. Same shape as `subscription.created`.

```json theme={null}
{
  "type": "subscription.renewed",
  "timestamp": "2025-08-01T12:00:00.000Z",
  "id": "evt_ghi789",
  "data": {
    "status": "active",
    "customerId": "cus_abc",
    "productId": "prod_xyz",
    "amount": 10000,
    "currency": "SAT",
    "recurringInterval": "MONTH",
    "currentPeriodEnd": "2025-09-01T00:00:00.000Z",
    "customer": {
      "email": "alice@example.com",
      "name": "Alice"
    }
  }
}
```

### subscription.canceled

Sent when a subscription is canceled.

```json theme={null}
{
  "type": "subscription.canceled",
  "timestamp": "2025-07-15T12:00:00.000Z",
  "id": "evt_jkl012",
  "data": {
    "status": "canceled",
    "customerId": "cus_abc",
    "productId": "prod_xyz",
    "cancelAtPeriodEnd": true,
    "canceledAt": "2025-07-15T12:00:00.000Z",
    "endsAt": "2025-08-01T00:00:00.000Z",
    "customer": {
      "email": "alice@example.com",
      "name": "Alice"
    }
  }
}
```

## Signature Verification

moneydevkit signs every webhook using the [Standard Webhooks](https://www.standardwebhooks.com/) specification. Each request includes three headers:

| Header              | Description                                      |
| ------------------- | ------------------------------------------------ |
| `webhook-id`        | Unique message ID (use for idempotency)          |
| `webhook-timestamp` | Unix timestamp (seconds) when the event was sent |
| `webhook-signature` | HMAC-SHA256 signature of the payload             |

The `standardwebhooks` library (used in the handler above) validates the signature, checks that the timestamp is recent (rejecting replay attacks), and returns the parsed payload.

## Retry Behavior

If your endpoint returns a non-2xx status code or the request times out, moneydevkit retries with increasing delays:

| Attempt   | Delay after failure |
| --------- | ------------------- |
| 1st retry | 5 seconds           |
| 2nd retry | 5 minutes           |
| 3rd retry | 30 minutes          |
| 4th retry | 2 hours             |
| 5th retry | 8 hours             |

After **5 consecutive failures** across any events, the endpoint is automatically disabled. You can re-enable it from the dashboard after fixing the issue.

## Managing Endpoints

From the dashboard (**Apps → your app → Webhooks**) you can:

* **Toggle** endpoints on/off without deleting them
* **Rotate** the signing secret if it's been compromised
* **Delete** endpoints you no longer need

<Tip>
  Respond to webhooks with a `2xx` status code as quickly as possible. Do any heavy processing asynchronously after acknowledging receipt. moneydevkit times out after 30 seconds — if your handler takes longer, the delivery will be retried.
</Tip>
