# Event Webhooks

> Register one endpoint to receive masking session and call lifecycle events. We POST signed JSON for session.created, call.incoming, call.connected, call.ended, call.missed and session.expired.

Source: https://edesy.in/docs/number-masking/sessions/event-webhooks

Event webhooks push **session and call lifecycle events** to a single endpoint you control, so your systems can react to masked activity — log a connected call, mark an order's calls complete, or alert on missed calls.

> Event webhooks are **outbound notifications** and are separate from the [Dynamic Routing Webhook](https://edesy.in/docs/number-masking/sessions/dynamic-routing), which is a real-time request we make *to decide who a call connects to*. You can use either, or both.

## Configure the endpoint

```
PUT /masking/event-webhook
```

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `url` | string | Yes | Your HTTPS endpoint. |
| `secret` | string | No | Signing secret for `X-Edesy-Signature`. Leave blank to keep the existing one. |
| `enabled` | boolean | No | Set `false` to pause deliveries without deleting the config. |

```bash
curl -X PUT https://voice-api.edesy.in/v1/masking/event-webhook \
  -H "Authorization: Bearer vp_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.yourapp.com/edesy/events",
    "secret": "whsec_supersecret"
  }'
```

**`200` — Saved**

```json
{ "data": { "url": "https://api.yourapp.com/edesy/events", "enabled": true, "has_secret": true } }
```

You can also set this in the portal under **Settings → Event webhook**.

## Events

| Event | Fires when |
|-------|-----------|
| `session.created` | A session is created. |
| `call.incoming` | A call reaches a virtual number. |
| `call.connected` | Both parties are bridged. |
| `call.ended` | A connected call ends. |
| `call.missed` | A call is not answered. |
| `session.expired` | A session expires or is ended. |

## Delivery payload

We POST JSON to your endpoint. Fields are use-case-agnostic — `reference` is your external id, and `caller`/`callee` carry the two parties:

```json
{
  "event": "call.ended",
  "reference": "ORD-4821",
  "virtual_number": "918071387146",
  "caller": "9876543210",
  "callee": "9123456789",
  "direction": "a_to_b",
  "call_sid": "d28be7cd-9840-4b23-9627-199f1ef81fc6",
  "duration_sec": 92,
  "status": "completed",
  "timestamp": "2026-07-14T18:05:00Z"
}
```

`direction` is `a_to_b` or `b_to_a`. Not every field is present on every event (for example, `duration_sec` only appears once a call has ended).

### Signature verification

Each delivery is signed with **HMAC-SHA256** over the raw body, sent in `X-Edesy-Signature`. Recompute the HMAC with your secret and compare before trusting the payload. Deliveries have a **5-second** timeout.

## Delivery log

```
GET /masking/event-webhook/deliveries
```

Returns a log of recent delivery attempts — event type, `reference`, URL, HTTP status code, whether it succeeded, and any error — so you can debug your integration.

## Send a test event

```
POST /masking/event-webhook/test
```

Sends a synthetic `session.created` event to your configured endpoint and returns the HTTP status, latency, and response body, so you can confirm your receiver works before real traffic arrives.

## Related

- [Dynamic Routing Webhook](https://edesy.in/docs/number-masking/sessions/dynamic-routing)
- [Two-Way Sessions](https://edesy.in/docs/number-masking/sessions)
