# Webhooks for your customers

URL: /products/api/webhooks · Id: `page:products/api/webhooks`

> Webhooks for an API's developers, delivered to their own receiver under the Startup's name, signed, retried for three days and replayable from any point.

A [Customer](/business/nouns/customer) hears a [Startup](/business/nouns/startup)'s [Events](/business/nouns/event) through a [Watch](/business/nouns/watch): a standing request to post each Event a selector matches to the Customer's URL. Deliveries carry the Startup's own signature header, retry for three days, and replay from any point.

## The catalog a receiver selects from
Every [Verb](/business/nouns/verb) the [Publication](/business/nouns/publication) exposes emits one Event named for its [Noun](/business/nouns/noun) and the past tense. Leaseline declares a `Lease` Noun, so `Lease.renew` emits `Lease.renewed`. That list is the Startup's Event catalog, in each Verb's reference.

A Watch hears only what its key may read. Harbor Residential's key reaches its own [Tenant](/business/nouns/tenant), so its Watch never sees another Customer's leases.

## Create a Watch
```bash
curl -X POST https://api.leaseline.app/watches \
  -H "Authorization: Bearer $LEASELINE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"selector":"Lease.renewed","url":"https://harborresidential.com/hooks/leaseline"}'
```

```json
{
  "api": "https://api.leaseline.app",
  "watch": {
    "$id": "https://api.leaseline.app/watches/watch_9o4vCuR17L",
    "$type": "Watch",
    "selector": "Lease.renewed",
    "url": "https://harborresidential.com/hooks/leaseline"
  },
  "secret": "whsec_7d2b9e4a1c6f3b8e0a5d2c9f4b7e1a3c",
  "links": { "self": "https://api.leaseline.app/watches/watch_9o4vCuR17L" }
}
```

The signing secret appears once. A selector names one Event or every Event of a Noun, `Lease.*`, and `where` narrows it: `Lease.renewed where rent.amount > 1500`.

## The delivery
```json
{
  "$id": "https://api.leaseline.app/events/evt_e7XmKZ6oGO",
  "$type": "Event",
  "name": "Lease.renewed",
  "at": "2026-10-03T14:10:00Z",
  "seq": 21,
  "data": {
    "lease": { "$id": "https://api.leaseline.app/leases/lease_qImdtSGZVN", "$type": "Lease", "status": "renewed" }
  }
}
```

```text
Leaseline-Signature: t=1791036600,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
```

Each `v1` is a hex HMAC-SHA256 of `t`, a dot and the raw body, keyed with the signing secret. Verify the raw bytes, accept any matching `v1`, and reject a timestamp older than five minutes.

## Retries, pauses and replay
Answer any `2xx` within 10 seconds. An unacknowledged delivery retries 12 times over three days, and each try is recorded with its status code.

A Watch whose deliveries all fail for three days pauses itself, and its Events keep queuing. [`Watch.resume`](/business/nouns/watch/resume) delivers them oldest first, and [`Watch.replay`](/business/nouns/watch/replay) sends everything since a chosen time again. Events are kept forever, so any point works.

[`Watch.rotate`](/business/nouns/watch/rotate) issues a new secret and signs with both for 24 hours. A Watch created with `"payload":"thin"` sends only the record's versioned address. [Webhooks](/code/api/guides/webhooks) has the full delivery rules.