# Event webhooks

> Beam pushes signed events to your endpoint the moment things happen: replies, sends, bookings, handoffs, opt-outs.

## Setup

Settings → Event webhooks → paste any HTTPS endpoint → Save. Beam generates a signing secret, shown right there. Every event is a POST with a JSON body and a Beam-Signature header. Respond with any 2xx quickly and process async.

## Envelope

Every eventCopy
```
{
  "id": "evt_8c1f2a9d64b34e0f9a12",
  "type": "message.received",
  "timestamp": 1787340000,
  "data": { ... event specific ... }
}
```

## Event catalog

  
| Type | When | data | 
  
| message.received | A contact texted you | from, to, body, channel, attachments | 
  
| message.sent | Any outbound went out (first touch, inbox, assistant, API) | id, to, from, body, channel, sender | 
  
| message.failed | An outbound could not be delivered | id, to, body | 
  
| contact.opted_out | A contact texted STOP | phone | 
  
| assistant.booked | The assistant booked a call | phone, window, appointment_id, slot | 
  
| assistant.handoff | The assistant needs a human | phone, reason | 

## Example: reply notifications in your own system

Node/Bun receiverCopy
```
Bun.serve({
  port: 3000,
  async fetch(req) {
    const raw = await req.text();
    if (!verifyBeamSignature(raw, req.headers.get("Beam-Signature"), process.env.BEAM_WEBHOOK_SECRET)) {
      return new Response("bad signature", { status: 401 });
    }
    const event = JSON.parse(raw);
    if (event.type === "assistant.booked") {
      // ping the sales floor, update the CRM, fire the AI call...
      notifyTeamChannel(`🎉 call booked with ${event.data.phone}: ${event.data.window}`);
    }
    return new Response("OK");
  },
});
```

Delivery guaranteesEvents are pushed once with a 5 second timeout and no automatic retries in this version. For anything you cannot afford to miss, reconcile with History & listing on a schedule. Signature verification: Webhook signing.
