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

# Events & webhooks

> Event-driven integration with the Brew Public API v1 — you-call-us today, we-call-you on the roadmap.

Brew's event model has **two directions** — you push events INTO Brew today (inbound), and Brew pushes events back to your endpoints on the roadmap (outbound). This page describes what works today and what's coming.

## Direction map (TL;DR)

| Direction                                                                                                                                                                                      | Who calls who                                                                     | Status     |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ---------- |
| **You → Brew** — fire a custom event from your backend so Brew runs the matching automation                                                                                                    | `POST /v1/automations/triggers/{triggerEventId}/fire { payload, idempotencyKey }` | ✅ Shipped  |
| **You → Brew** — managed integration receivers (Clerk, Stripe, Shopify, Stytch, Supabase, WorkOS, RevenueCat) push their events INTO Brew through a Brew-hosted receive URL you configure once | Per-integration setup in the [Integrations tab](/integrations/integrations)       | ✅ Shipped  |
| **Brew → You** — outbound webhooks for run / send / email-event lifecycle (`run.completed`, `send.delivered`, `send.opened`, …)                                                                | Coming — see [roadmap](#outbound-webhooks-roadmap)                                | 🚧 Planned |
| **Brew → You** — SSE / WebSocket streaming for live run logs                                                                                                                                   | Coming with outbound webhooks                                                     | 🚧 Planned |

Until outbound webhooks ship, the supported "wait for completion" pattern is polling — see [Async jobs & polling](/api-reference/api/async-jobs).

## Inbound — you call Brew

### Pattern: fire a custom event

The canonical agent / backend integration path:

```
your-backend → POST  /v1/automations/triggers              → mint triggerEventId
your-backend → POST  /v1/emails                            → mint email bodies (untyped designs)
your-backend → GET   /v1/domains                           → pick verified sending domain
your-backend → POST  /v1/automations                       → assemble graph (trigger → sendEmail → wait → …)
your-backend → PATCH /v1/automations/{automationId}        → { published: true } to go live
your-backend → POST  /v1/automations/triggers/{id}/fire    → fire whenever the real-world event happens
```

The fire call:

```bash theme={null}
curl -X POST https://brew.new/api/v1/automations/triggers/tri_signup/fire \
  -H "Authorization: Bearer $BREW_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: signup-jane@example.com-1779292800" \
  -d '{
    "payload": { "email": "jane@example.com", "firstName": "Jane" }
  }'
# → 200 { success: true, status: "triggered",
#         details: { triggerInstanceId, automationRunIds: ["run_..."], ... } }
```

Key invariants:

* **`payload.email` is the contact key.** Every trigger's `payloadSchema.fields` MUST declare `{ key: 'email', type: 'string', required: true }`. The fire endpoint also auto-upserts the contact derived from the resolved payload — declared `firstName` / `lastName` / `subscribed` land in core columns; everything else lands in `customFields[key]` (with the field definition auto-created on the brand).
* **`Idempotency-Key` is mandatory in practice.** A doubled-fired webhook = doubled emails. See [Idempotency](/api-reference/api/idempotency).
* **The fire endpoint returns the legacy fire envelope** (`200`) with `details.automationRunIds[]` — it's the one v1 endpoint that wraps both success and failure in this shape. Don't block on delivery — poll the run with [Async jobs & polling](/api-reference/api/async-jobs).
* **At least one bound automation must be Published.** Triggers are always-on, but emails only fire when at least one bound automation has `published: true`. If every bound automation is in Draft, the fire call returns `422 NO_PUBLISHED_AUTOMATION`. Publish the automation in the builder (or via `brew.automations.patch({ published: true })`) and retry.

### Pattern: integration-provisioned triggers

For events that originate inside another product (Clerk's `user.created`, Stripe's `customer.subscription.deleted`, Shopify's `orders/paid`, etc.), Brew can RECEIVE the integration's webhook directly — no code on your side.

Setup is per-integration in the dashboard (the receiver URL is hosted at `/api/integrations/<provider>/webhook/<connectionId>`). Once configured, the integration's events appear in `GET /v1/automations/triggers` with `provider: 'clerk' | 'stripe' | 'shopify' | …` and fire your published automations exactly like custom `brew_api` triggers.

You can bind any of these to an automation. Supported providers today:

| Provider   | Common events                                                                       |
| ---------- | ----------------------------------------------------------------------------------- |
| Clerk      | `user.created`, `user.updated`, `organization.created`                              |
| Stripe     | `customer.subscription.created/updated/deleted`, `invoice.paid`, `customer.deleted` |
| Shopify    | `orders/paid`, `customers/create`, `customers/update`                               |
| Stytch     | `user.created`, `user.deleted`, `session.created`                                   |
| Supabase   | `INSERT`, `UPDATE`, `DELETE` on configured tables                                   |
| WorkOS     | `dsync.user.created`, `dsync.user.updated`, `dsync.user.deleted`                    |
| RevenueCat | `INITIAL_PURCHASE`, `RENEWAL`, `CANCELLATION`, `EXPIRATION`                         |

Each provider's adapter normalises into a flat `{ email, …provider-specific fields }` payload that flows through the same fire pipeline as `brew_api` triggers. **Inbound integration webhooks also auto-upsert the contact** using a richer per-provider mapper that lands under flat `<provider>_*` custom field names (e.g. `clerk_user_id`, `stripe_customer_id`).

## Outbound webhooks (roadmap)

We're shipping outbound webhooks so your backend can be notified when emails are delivered, opened, clicked, bounced, or marked as spam — and when automation runs complete or fail. Below is the **planned contract**; nothing here is live yet. Subscribe to the [changelog](/changelog) for the launch announcement.

### Planned event types

| Event                                                        | Fires when                                                       |
| ------------------------------------------------------------ | ---------------------------------------------------------------- |
| `email.sent`                                                 | An email is handed to the upstream ESP.                          |
| `email.delivered`                                            | The recipient's MX accepted the message.                         |
| `email.opened`                                               | The recipient opened the message (one event per open).           |
| `email.clicked`                                              | The recipient clicked a tracked link.                            |
| `email.bounced`                                              | Hard or soft bounce.                                             |
| `email.complained`                                           | The recipient marked the message as spam.                        |
| `email.unsubscribed`                                         | The recipient unsubscribed via the footer link.                  |
| `send.queued`                                                | A `POST /v1/sends` campaign was accepted.                        |
| `send.scheduled`                                             | A scheduled send was queued for future delivery.                 |
| `send.completed`                                             | All recipients of a send job were handled.                       |
| `send.failed`                                                | A send job failed to start (e.g. domain not ready).              |
| `run.started`                                                | An automation run started a workflow.                            |
| `run.completed`                                              | A run finished all nodes successfully.                           |
| `run.failed`                                                 | A run errored out at a node.                                     |
| `run.cancelled`                                              | A run was cancelled (P7 — when cancel ships).                    |
| `contact.created` / `contact.updated` / `contact.suppressed` | Contact lifecycle events.                                        |
| `trigger.fired`                                              | A trigger fire was accepted (before any per-automation routing). |

### Planned delivery contract

* **Signed.** Every webhook will carry an HMAC signature header (`X-Brew-Signature`) and a timestamp (`X-Brew-Timestamp`) so you can verify origin.
* **Retried.** Exponential back-off (1s / 5s / 30s / 5m / 1h / 6h) until a 2xx response, then dropped after 24h.
* **Per-subscription delivery logs.** Inspect deliveries + replay failed ones from the dashboard.
* **Idempotent.** Every event carries a unique `event.id`; consumers should dedupe.

### Planned subscription management API

```http theme={null}
POST   /v1/webhook-subscriptions     { url, events: [...], description? }
GET    /v1/webhook-subscriptions
PATCH  /v1/webhook-subscriptions     { subscriptionId, url?, events?, enabled? }
DELETE /v1/webhook-subscriptions     { subscriptionId }
POST   /v1/webhook-subscriptions/rotate-secret { subscriptionId }
GET    /v1/webhook-subscriptions/deliveries?subscriptionId=…  // delivery log
```

### Planned envelope

```json theme={null}
{
  "id": "evt_01HZ…",
  "type": "send.delivered",
  "createdAt": "2026-04-08T12:34:56.789Z",
  "data": {
    "sendId": "snd_…",
    "emailId": "eml_promo",
    "recipientEmail": "jane@example.com",
    "messageId": "<resend-msg-id>"
  }
}
```

## What to do today

| Need                                                                    | Today's pattern                                                                                                                        |
| ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| React to inbound events (signup, purchase, churn) and run an automation | `POST /v1/automations/triggers/{triggerEventId}/fire` — works now                                                                      |
| Wait for a fire to complete                                             | Poll `GET /v1/automations/runs?automationRunId=…` — see [Async jobs](/api-reference/api/async-jobs)                                    |
| Receive provider webhooks (Stripe, Clerk, Shopify, …) into Brew         | Configure the integration in the dashboard; Brew hosts the receive URL                                                                 |
| Notify your backend when an email opens / delivers                      | Roadmap — outbound webhooks. Until they ship, use the dashboard analytics, or the ESP's own webhook (Resend) if you have direct access |
| Stream live run logs into a UI                                          | Roadmap — SSE. Today: poll `GET /v1/automations/runs?automationRunId=…&include=logs`                                                   |

## Security checklist (will apply when outbound webhooks ship)

* Verify the HMAC signature on every webhook (header `X-Brew-Signature`).
* Treat the timestamp as a freshness check — reject deliveries older than 5 minutes.
* Dedupe on `event.id` — Brew may retry on `2xx` slowness or your own brief outage.
* Return `2xx` only when you've persisted enough state to survive a restart; return `4xx` for permanent rejections so we don't retry.

## See also

* [Authentication](/api-reference/api/authentication) — how `Authorization: Bearer` works on the inbound side.
* [Async jobs & polling](/api-reference/api/async-jobs) — the supported "wait for completion" pattern today.
* [Idempotency](/api-reference/api/idempotency) — pair with every inbound retry.
* [Integrations](/integrations/integrations) — set up provider receivers (Clerk, Stripe, Shopify, …).

## Need Help?

Our team is ready to support you at every step of your journey with Brew. Choose the option that works best for you:

<Tabs>
  <Tab title="Self-Service Tools">
    <CardGroup cols="2">
      <Card title="Search Documentation" icon="magnifying-glass" color="#c44925">
        Type in the "Ask any question" search bar at the top left to instantly find relevant documentation pages.
      </Card>

      <Card title="ChatGPT/Claude Integration" icon="robot" color="#c44925">
        Click "Open in ChatGPT" at the top right of any page to analyze documentation with ChatGPT or Claude for deeper insights.
      </Card>
    </CardGroup>
  </Tab>

  <Tab title="Talk to Our Team">
    <CardGroup cols="2">
      <Card title="Schedule a Call" icon="calendar" color="#c44925" href="https://calendar.google.com/calendar/u/0/appointments/schedules/AcZssZ1iYoRUG1J792XQpbuQLjSRRDupr7MwraFK-HQRCtTYdBmrQi8nZu2qXfzKQigb8gbKJK3KN3-R">
        Book time with our founders for personalized guidance on strategy, best practices, or complex implementation questions.
      </Card>

      <Card title="Call Us Directly" icon="phone" color="#c44925">
        Need immediate assistance? Reach us at **+1-(332)-203-2145** for urgent issues or time-sensitive questions.
      </Card>

      <Card title="Slack Channel" icon="slack" color="#c44925">
        Our preferred support channel. You'll receive an invite after signup for direct founder support and fast responses.
      </Card>

      <Card title="Email Support" icon="envelope" color="#c44925" href="mailto:support@brew.new">
        Contact us at **[support@brew.new](mailto:support@brew.new)** for detailed inquiries or if you prefer not to use Slack.
      </Card>
    </CardGroup>
  </Tab>
</Tabs>
