YourNextHome

Webhooks

Receive YourNextHome events at your own HTTPS endpoint.

Webhooks let YourNextHome push events to a URL you control as soon as something happens — for v1, that means a new inquiry. You no longer need to poll our API to find out a lead came in.

What ships in v1

A single event type:

EventWhen it fires
inquiry.createdA visitor submits the inquiry form on one of your viewers or embeds.

The signing scheme, headers, and delivery guarantees described below are designed to extend — adding new event types later will not break existing integrations.

Configure an endpoint

  1. Sign in to the dashboard as an organization admin.
  2. Open Settings → Webhooks.
  3. Generate the signing secret and copy it once — we never show it again. Store it in your secrets manager.
  4. Click Add endpoint, paste your HTTPS URL, enable inquiry.created, and save.

The URL must be HTTPS and must resolve to a public address. We refuse loopback (127.0.0.0/8), private (10/8, 172.16/12, 192.168/16, fc00::/7), and link-local (169.254/16, fe80::/10) ranges to prevent abuse.

Receiving a webhook

Each delivery is an HTTPS POST with a JSON body.

Request headers

HeaderValue
Content-Typeapplication/json
User-AgentYourNextHome-Webhooks/1.0
X-YNH-Event-IdUUID, stable across retries — use it for receiver-side dedup
X-YNH-Event-Typee.g. inquiry.created
X-YNH-Delivery-IdUUID, unique per attempt
X-YNH-Attempt1-based attempt counter
X-YNH-TimestampUnix seconds, signed alongside the body
X-YNH-Signaturet=<timestamp>,v1=<hex(hmac_sha256(secret, "<ts>.<body>"))>

Body (inquiry.created)

{
  "id": "11111111-2222-3333-4444-555555555555",
  "type": "inquiry.created",
  "created": "2026-05-13T10:00:00.000Z",
  "data": {
    "inquiry": {
      "id": "...",
      "name": "Jane Doe",
      "email": "jane@example.com",
      "phoneNumber": "+1 555 123 4567",
      "info": "Interested in the corner unit.",
      "apartmentIds": ["..."],
      "estateId": "...",
      "referralUserId": null,
      "createdAt": "2026-05-13T09:59:58.500Z"
    }
  }
}

We deliberately omit ip and userAgent — PII you don't need to receive.

Verify the signature

Always recompute the HMAC and compare with timingSafeEqual — never trust the X-YNH-Signature header verbatim.

import crypto from 'node:crypto';

function verify(req, secret) {
  const sigHeader = req.headers['x-ynh-signature'];
  const [tPart, v1Part] = sigHeader.split(',');
  const ts = Number(tPart.slice(2));
  const v1 = v1Part.slice(3);

  // Reject events older than 5 minutes (clock-skew + replay protection)
  if (Math.abs(Date.now() / 1000 - ts) > 300) {
    throw new Error('stale webhook timestamp');
  }

  // req.rawBody must be the exact bytes we received — do not re-serialize
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${ts}.${req.rawBody.toString('utf8')}`)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(v1, 'hex'))) {
    throw new Error('bad webhook signature');
  }
}

A quick curl check against a freshly delivered payload (handy for ops):

TIMESTAMP=$(date -r 1715616000 +%s)
PAYLOAD='{"id":"...","type":"inquiry.created"}'
SECRET='whsec_replace_me'
SIG=$(printf '%s.%s' "$TIMESTAMP" "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
echo "t=$TIMESTAMP,v1=$SIG"

Delivery guarantees

  • At-least-once. Receivers must dedup by X-YNH-Event-Id.

  • No ordering guarantee between events. A replay can arrive after a newer event.

  • Up to 6 attempts spaced out over ~30 hours, with ±20 % jitter on each delay:

    AttemptDelay from previousCumulative
    100
    21 min1 min
    35 min6 min
    430 min36 min
    53 h~4 h
    624 h~28 h
  • Success = HTTP 2xx. Anything else (4xx, 5xx, network error, timeout) counts as a failure and triggers the next attempt.

  • Response capture. We record HTTP status + the first 2 KB of the body — handy for debugging your handler.

  • Auto-disable. After 20 consecutive dead deliveries we disable the endpoint and stop sending. Re-enable it from the dashboard once you've fixed your handler.

  • Retention. We keep delivery logs for 30 days.

Rotating the secret

Open Settings → Webhooks → Rotate signing secret. The old secret stops working immediately — there is no overlap window in v1. Update every consumer to the new secret before rotating in production.

If you lose the secret, just rotate. There is no "reveal again" endpoint.

Replay

From the deliveries drawer on a given endpoint, click Replay on any past attempt. We insert a fresh delivery row that reuses the original X-YNH-Event-Id. Your receiver's dedup logic should treat the replay as a no-op for events you've already processed.

Troubleshooting

SymptomFix
Endpoint is Auto-disabledLook at recent deliveries for the failure reason, fix your handler, then re-enable from the dashboard. The failure counter resets on the next success.
All attempts show endpoint disabledThe endpoint was paused/disabled between dispatch and the attempt. Re-enable and replay.
Signature check failsYou're probably parsing the body as JSON and re-serializing it before signing. Sign the raw bytes we sent.
Receiver returns 200 but never sees the rowYour handler is acking before processing. Process synchronously or use your own outbox.

On this page