# EXR Commercial API Webhooks

## Webhooks

**Supported events (36):**

- `deal.created`
- `deal.updated`
- `deal.deleted`
- `tenant.created`
- `tenant.updated`
- `tenant.deleted`
- `prospect.created`
- `prospect.updated`
- `prospect.deleted`
- `client.created`
- `client.updated`
- `client.deleted`
- `workspace.created`
- `workspace.updated`
- `workspace.deleted`
- `action.created`
- `action.updated`
- `action.deleted`
- `campaign.created`
- `campaign.updated`
- `campaign.deleted`
- `broker.created`
- `broker.updated`
- `broker.deleted`
- `brokerage.created`
- `brokerage.updated`
- `brokerage.deleted`
- `entity.created`
- `entity.updated`
- `agreement.created`
- `agreement.updated`
- `agreement.deleted`
- `agreement.sent`
- `agreement.signed`
- `agreement.completed`
- `agreement.voided`

- Entities emit entity.created and entity.updated only; merge/dedupe is in-app and there is no entity.deleted event.
- Deal, tenant, prospect, workspace, and agreement CRUD events may originate from in-app or public API writes.
- Broker, brokerage, action, campaign, entity, and client events currently originate only from public API writes.
- Agreement sent, signed, completed, and voided events originate in the shared state machine and may be triggered in-app or through the public API.

### Envelope

```json
{ "id": "…", "type": "deal.created", "createdAt": "…", "apiVersion": "v1", "data": { "deal": { "…": "…" } } }
```

Delivery is attempted 4 times (immediate → +30 s → +2 min → +10 min), with a 10-second timeout per attempt. Any HTTP 2xx response acknowledges a delivery.

### Signature verification

```text
signedPayload = <t>.<rawBody>
v1            = HMAC-SHA256(signing_secret, signedPayload)  // hex
header        = X-EXR-Signature: t=<unix-ts>,v1=<hex>
```

Reject stale timestamps and compare the signature with a timing-safe comparison. Never put signing secrets in source control or browser code.
