# Webhooks

Register an HTTPS endpoint to be notified when a job finishes, instead of polling. MapLeads emits `search.completed` and `search.failed`. Managing webhooks requires the `webhooks` scope.

`GET /v1/webhooks`
`POST /v1/webhooks`
`DELETE /v1/webhooks/:id`

### Create body

| Field | Type | Description |
| --- | --- | --- |
| `url` | string | HTTPS endpoint that receives deliveries. |
| `eventTypes` | string[] | search.completed and/or search.failed. |

```
curl https://api.mapleads.ai/v1/webhooks \
  -X POST \
  -H "Authorization: Bearer mapleads_xxx" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/hooks/mapleads","eventTypes":["search.completed"]}'
```

The signing `secret` is returned once, in this create response. Each delivery is a JSON POST with these headers:

| Header | Type | Description |
| --- | --- | --- |
| `x-mapleads-event` | string | The event type. |
| `x-mapleads-delivery` | string | Unique delivery id (use it to de-duplicate). |
| `x-mapleads-signature` | string | sha256=<HMAC-SHA256 of the raw body with your secret>. |

Verify the signature before trusting a delivery:

```js
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(rawBody, header, secret) {
  const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(header ?? "");
  return a.length === b.length && timingSafeEqual(a, b);
}
```
