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

# Set Up Email Webhooks

> Receive real-time events when emails are delivered, bounced, opened, or clicked

Subscribe to events from Hiveku's email service so your app can react in real time — log deliveries, handle bounces, fire workflows when a lead opens an email, and more.

## Add a Webhook

<Steps>
  <Step title="Open the Webhooks tab">
    In your project, go to **Settings > Email Service > Webhooks**.
  </Step>

  <Step title="Click Add Webhook">
    Click **+ Add Webhook** to open the configuration form.
  </Step>

  <Step title="Enter the endpoint URL">
    Your webhook URL must be publicly reachable over HTTPS. Localhost and private IPs won't work — use a tunnel like ngrok for local testing.
  </Step>

  <Step title="Select events">
    Pick the events you want. Choose specific events or `*` for everything:

    * `delivery` — message was accepted by the recipient's mail server
    * `bounce` — message was rejected (hard or soft)
    * `complaint` — recipient marked the message as spam
    * `open` — recipient opened the email (tracking pixel)
    * `click` — recipient clicked a tracked link
    * `*` — all events
  </Step>

  <Step title="Optional: filter by tag">
    Narrow the webhook to emails tagged with specific labels — useful when different parts of your app need different event streams (e.g., a campaign webhook vs. a transactional webhook).
  </Step>

  <Step title="Save">
    Click **Save**. Hiveku generates a signing secret — copy it and store it as an env var for your webhook handler.
  </Step>
</Steps>

## Webhook Payload

Each event POSTs JSON to your endpoint:

```json theme={null}
{
  "event_id": "evt_01J9Q2K8X3M4N5P6Q7R8S9T0",
  "event_type": "delivery",
  "timestamp": "2026-04-17T12:34:56Z",
  "email_id": "msg_01J9Q2K8X3M4N5P6Q7R8S9T1",
  "to": "customer@example.com",
  "from": "hello@yoursite.com",
  "subject": "Your receipt",
  "tags": ["transactional", "receipt"],
  "metadata": {
    "provider_message_id": "0100018f-abc...",
    "smtp_response": "250 2.0.0 OK"
  }
}
```

Different event types include additional fields — for `click` events, you'll also get a `url` field with the clicked link.

## Verify Signatures

Every request includes an `X-Hiveku-Signature` header with an HMAC-SHA256 of the raw request body. Verify it before trusting the payload.

```typescript theme={null}
import crypto from 'crypto';

function verifyWebhook(
  rawBody: string,
  signature: string,
  secret: string
): boolean {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

// In your webhook handler:
export default async function handler(req: Request) {
  const rawBody = await req.text();
  const signature = req.headers.get('x-hiveku-signature');

  if (!signature || !verifyWebhook(rawBody, signature, process.env.HIVEKU_WEBHOOK_SECRET!)) {
    return new Response('Invalid signature', { status: 401 });
  }

  const event = JSON.parse(rawBody);
  // handle event...

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

<Warning>
  Always verify against the **raw** request body, not the parsed JSON. Parsing and re-serializing changes whitespace and key order, which breaks the signature.
</Warning>

## Retry & Reliability

Hiveku retries failed deliveries with exponential backoff when your endpoint returns a 5xx status or times out.

* Retries continue for up to 24 hours
* Backoff starts at 30 seconds, doubles each attempt
* Hiveku stops retrying after a successful 2xx response

If your endpoint returns 4xx or 5xx repeatedly, the webhook **auto-disables**. You'll see the disabled status in the dashboard — fix the endpoint and re-enable.

<Info>
  Because events can be retried, your handler should be idempotent. Use the `event_id` field as a deduplication key.
</Info>

## Monitor Webhook Health

The Webhooks tab shows per-webhook metrics:

* Success rate (2xx responses)
* Failure rate (4xx/5xx)
* Average latency
* Last successful delivery
* Last failure and reason

Hover over any row to see recent delivery attempts and response bodies — handy for debugging.

## Filter by Tag

When you send an email, you can attach tags (`tags: ['campaign-q2', 'newsletter']`). Webhooks configured with tag filters only fire for matching emails.

Typical use:

* One webhook for transactional emails (receipts, auth) → logs to your analytics DB
* Another for campaigns → updates CRM engagement scores
* Another for cold outreach → triggers follow-up workflows

## Verify Your Webhook

<Steps>
  <Step title="Send a test email">
    Use the dashboard **Send Test** button or call the email API with a real recipient.
  </Step>

  <Step title="Watch your endpoint">
    Within a few seconds of delivery, your webhook should fire with a `delivery` event. Check logs to confirm you received and verified it.
  </Step>
</Steps>

See the [email webhooks reference](/email/webhooks) for the full event schema.

## Troubleshooting

<AccordionGroup>
  <Accordion title="No events arriving">
    Your URL must be publicly accessible HTTPS. `localhost`, `127.0.0.1`, and private IP ranges are blocked. Use ngrok, Cloudflare Tunnel, or deploy to a staging URL for local testing.
  </Accordion>

  <Accordion title="Signature verification fails">
    Use the raw request body, not the parsed JSON. Call `req.text()` and verify against that string, then parse separately. Middleware that auto-parses JSON before your handler will break verification.
  </Accordion>

  <Accordion title="Webhook auto-disabled">
    Too many consecutive failures. Fix your endpoint (check logs for the error), then click **Re-enable** on the webhook row. Consider adding error-handling upstream so a single bug doesn't take down your webhook.
  </Accordion>

  <Accordion title="Receiving duplicate events">
    Expected during retries if your handler's first response was slow or ambiguous. De-duplicate using the `event_id` field — store processed IDs for 24 hours and skip duplicates.
  </Accordion>

  <Accordion title="Missing some event types">
    Double-check the events you subscribed to. `open` and `click` events require email tracking to be enabled on the original send. Transactional emails often have tracking off by default.
  </Accordion>
</AccordionGroup>

## What's Next?

<CardGroup cols={2}>
  <Card title="Send Emails" icon="paper-plane" href="/how-tos/send-emails">
    Start sending emails that fire webhook events
  </Card>

  <Card title="Email Webhooks Reference" icon="code" href="/email/webhooks">
    Full event schema and API details
  </Card>
</CardGroup>
