Webhooks

Every event, the delivery format, signature verification and retries.

Webhooks

A webhook is the opposite direction from the API: instead of you calling us, we POST to a URL of yours when something happens on your account. No API key is involved — a delivery is trusted because it is signed, not because it is authenticated.

Endpoints are managed in the account area under Settings → Webhooks, or over the API; see Webhook endpoints.

Events

Subscribe an endpoint to the events you care about; anything you did not subscribe to is never sent.

Domains

Event Sent when
domain.created A listing is added
domain.updated A listing changes
domain.sold A listing moves to status sold
domain.deleted A listing is deleted

Leads

Event Sent when
lead.created A new enquiry arrives
lead.updated The lead record changes
lead.status_changed Its status changes
lead.deleted It is moved to the trash
lead.replied You answer the buyer

Messages

Event Sent when
lead.message_received The buyer writes back
lead.message_sent A reply goes out

Subscription and payments

Event Sent when
subscription.created A plan starts
subscription.updated A plan changes
subscription.cancelled A plan ends
payment.received A payment succeeds
payment.failed A payment fails

Billing is not live yet, so these five are never delivered today. They can already be subscribed to, so an integration is ready when billing arrives.

What a delivery looks like

POST /hooks/ydomain HTTP/1.1
Host: example.com
Content-Type: application/json
User-Agent: ydomain-webhooks/1.0
X-Ydomain-Event: lead.created
X-Ydomain-Signature: t=1789595100,v1=6f1a8c4e2b7d09f3a5c8e1b4d7a2f9c6e3b0d8a5f2c7e4b1d9a6f3c0e7b4d1a8

{
  "event": "lead.created",
  "created_at": "2026-09-14T11:02:19+00:00",
  "data": {
    "id": "01920fc4-7b21-7d90-8e55-9a1c3f7d2b08",
    "domain": "example.net",
    "sender_name": "Pieter Jansen",
    "offer_amount": 1500,
    "offer_currency": "EUR"
  }
}

The envelope is always event, created_at and data. What sits inside data depends on the event: a lead event carries at least lead_id, a domain event carries domain. Treat data as a pointer, not as the whole record — read the record back over the API if you need every field.

Answer with any 2xx within ten seconds. Queue your own work instead of doing it inline; a slow answer counts as a failure.

Verifying a delivery

Never trust a delivery you have not verified. Sign the timestamp and the raw body with the endpoint's signing secret and compare in constant time.

signed_payload = timestamp + "." + raw_request_body
expected       = hmac_sha256(secret, signed_payload)
valid          = hash_equals(expected, v1) and abs(now - timestamp) <= 300

PHP:

[$t, $v1] = sscanf($request->header('X-Ydomain-Signature'), 't=%d,v1=%s');

$expected = hash_hmac('sha256', $t.'.'.$request->getContent(), $secret);
$valid = hash_equals($expected, $v1) && abs(time() - $t) <= 300;

Node:

const [, t, v1] = /t=(\d+),v1=([a-f0-9]+)/.exec(req.headers['x-ydomain-signature']);
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const valid = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))
  && Math.abs(Date.now() / 1000 - Number(t)) <= 300;

Two things go wrong most often:

The secret is shown in the account area under Settings → Webhooks: open the endpoint and press Show. Rotate replaces it, at which point deliveries signed with the old secret stop validating, so update your receiver first.

Retries and failures

Deliveries are at-least-once: a receiver that timed out after doing its work will see the same event again. Key your handling on the event plus the record id and make it idempotent.

Managing endpoints

Endpoints are added, edited and deleted in the account area under Settings → Webhooks. The same three actions are available over the HTTP API; see Webhook endpoints.