A webhook is an HTTPS endpoint you host. When something happens in Nyumba Zetu
that you have subscribed to — a payment arrives, an invoice is issued, an
allocation is reversed — the platform signs a small JSON body and sends it to
that endpoint as a POST.
Register an endpoint, fire a test event, verify the signature.
The algorithm, with Node, Python and PHP implementations.
The seven subscribable events and an example payload for each.
Timeouts, backoff, what is permanent, and dead-lettering.
Webhooks are a change feed
This is the single most important thing to understand before you design around them. A subscription is a forward-looking feed of state changes, and it is not a data export.
When to use webhooks, and when to poll
- You want to react promptly to a change — notify a resident, kick off a reconciliation, refresh a cache.
- The fact that something changed is what you need, and you can fetch the detail yourself.
- You can host an endpoint that answers quickly and tolerates being called more than once for the same event.
- You need history, including anything from before you subscribed.
- You need the complete record, not a reference to it.
- You need a guaranteed-complete set to reconcile against — a ledger export, a month-end total, a warehouse load.
- You cannot host a public HTTPS endpoint.
Most integrations use both: the webhook is the trigger, the partner API is the source of the record.
How a delivery is put together
You register an endpoint
POST /v3/webhookswith your HTTPS URL and the event names you want. The response contains the signing secret once. See Managing endpoints.Something happens in the platform
A state change is recorded and published for delivery. Only events the catalogue marks as publicly subscribable can leave the platform — see the events reference.
The platform signs and sends
The body is serialised once and signed with your secret, then
POSTed to your URL withx-nz-webhook-timestampandx-nz-webhook-signature.Your endpoint verifies and answers 2xx
Verify the signature against the raw body, do your work asynchronously, and return a
2xxquickly. Anything else is treated as a failed delivery — see Retries and failures.
How deliveries behave
The mechanism, stated plainly — none of this is a service level.
Signed, always. Every delivery carries x-nz-webhook-signature. Signing is
fail-closed: a webhook whose secret cannot be resolved is not sent unsigned, it
is not sent at all.
HTTPS only, public addresses only. Your URL must be https, must not contain
credentials, and must not resolve to a private, loopback, link-local or reserved
address. This is re-checked on every delivery, not only at registration, and
the cleared address is pinned for the request.
At least once, not exactly once. A delivery that fails is retried. A 2xx
your server sent but that never reached the platform is a failure from the
platform's side, so the same event can arrive twice. Deduplicate on
executionId — it is stable across every retry of the same event.
Per-endpoint retry budget. Each registered endpoint has its own delivery queue, retry counter and dead-letter state. A failing endpoint exhausts its own budget and does not slow down anyone else's.
No ordering guarantee. Deliveries are independent and retried independently, so events can arrive out of the order in which they occurred. Use the ids and timestamps in the payload rather than arrival order.
Scope and permissions
A webhook belongs to exactly one organisation, resolved from your access token —
never from the request body. Optionally it can be narrowed to a single branch
with branchId; omit it to receive the whole organisation.