# Webhooks

Receive real-time notifications for email events (delivery, bounces, complaints, opens, clicks).

## Overview

Wraps uses AWS EventBridge to capture SES events and forward them to your webhook URL. Events include: Delivery, Bounce, Complaint, Open, Click, Send, Reject, Subscription.

Event flow: SES → EventBridge → Lambda → your webhook URL

## Prerequisites

- Email infrastructure deployed (`wraps email init`)
- An HTTPS URL that can receive POST requests
- Webhook secret stored in your environment

## Setup

Configure your webhook URL in the Wraps dashboard under **Settings → Webhooks → Add Webhook**. Enter your HTTPS URL and generate a webhook secret.

## Webhook Payload

Every event is a POST request with `Content-Type: application/json`:

```json
{
  "event": "Delivery",
  "detail": {
    "delivery": {
      "timestamp": "2024-01-15T10:30:00.000Z",
      "processingTimeMillis": 1234,
      "recipients": ["user@example.com"],
      "smtpResponse": "250 2.0.0 OK",
      "reportingMTA": "a8-31.smtp-out.amazonses.com"
    },
    "mail": {
      "messageId": "abc-123-def",
      "source": "hello@yourapp.com",
      "destination": ["user@example.com"]
    }
  },
  "timestamp": "2024-01-15T10:30:00Z",
  "messageId": "abc-123-def",
  "source": "wraps"
}
```

## Signature Verification

Every request includes an `x-wraps-signature` header. Always verify it:

```typescript
import crypto from "crypto";

function verifySignature(req: Request, secret: string): boolean {
  const signature = req.headers.get("x-wraps-signature");
  if (!signature || !secret) return false;

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(secret)
  );
}
```

## Example: Express.js Handler

```typescript
import express from "express";
import crypto from "crypto";

const app = express();
app.use(express.json());

const WEBHOOK_SECRET = process.env.WRAPS_WEBHOOK_SECRET;

app.post("/webhooks/email", (req, res) => {
  const signature = req.headers["x-wraps-signature"];
  if (!signature || !crypto.timingSafeEqual(
    Buffer.from(signature as string),
    Buffer.from(WEBHOOK_SECRET!)
  )) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  const { event, messageId, detail } = req.body;

  switch (event) {
    case "Delivery":
      console.log(`Email ${messageId} delivered`);
      break;
    case "Bounce":
      console.log(`Email ${messageId} bounced`);
      // Remove from your list
      break;
    case "Complaint":
      console.log(`Email ${messageId} complained`);
      // Unsubscribe the user
      break;
  }

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

## Example: Next.js Route Handler

```typescript
import crypto from "crypto";
import { NextResponse } from "next/server";

export async function POST(request: Request) {
  const signature = request.headers.get("x-wraps-signature");
  const secret = process.env.WRAPS_WEBHOOK_SECRET!;

  if (!signature || !crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(secret)
  )) {
    return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
  }

  const payload = await request.json();
  const { event, messageId } = payload;

  // Handle the event...
  console.log(`Received ${event} for message ${messageId}`);

  return NextResponse.json({ received: true });
}
```

## Troubleshooting

- **No events received**: Check EventBridge rules are active (`wraps email status`)
- **Signature mismatch**: Ensure you're comparing the raw header value to your stored secret
- **Timeout errors**: Respond with 200 immediately, process async

## Next Steps

- [Email SDK Reference](https://wraps.dev/docs/sdk-reference)
- [Email Events API](https://wraps.dev/docs/sdk-reference#email-events)
