Shipping daily
Nov 1DATAAll 646 projects re-verified: 6,419 developer claims tested against registers, courts and pressNov 1DATAMalaysia TEDUH and Philippines DHSUD licence checks: expired licences and approved delays foundNov 1DATAThailand ONEP register: 2 condos found selling before environmental approvalNov 1DATAMexico PROFECO contract register checks: one sales registration belonged to a laundryNov 1DATAAbu Dhabi ADREC inspections and RAK RERA permit list added: % built straight from the regulatorNov 1SHIPPEDNew stage filter: completed but still sellingNov 1SHIPPEDPublic database page: verification levels, disclosure distribution, every marketNov 1DATA+212 projects in one day, round 3: USA, Caribbean, Gulf, Africa, Japan, AustraliaNov 1SHIPPEDStage on every case: off-plan or under constructionOct 31SHIPPEDVerification level per check on every case card: registry, independent, developer, contradicted
Changelog →

Kolaboro / Docs / Webhooks

Signed webhooks

Partners with an API key can register an https URL and a list of tracked projects. When a project's public record changes, we POST a JSON event signed with the same Ed25519 key and scheme as the open API, timestamped and safe against replay. Every attempt is logged where you can see it.

Checking status…

A real signed event, verified in your browser

This is a sample event signed just now by our server with the production key. Your browser fetches the public key from /.well-known/kolaboro-keys.json and checks the signature with WebCrypto.

Loading a signed sample…

What we send

POST to your URL with content-type: application/json, a 10 second timeout and no redirects followed. Any 2xx answer counts as delivered.

HeaderMeaning
x-kolaboro-signatureBase64 Ed25519 signature over "kolaboro-sig-v1\n" + SHA-256 hex of the exact body. Same scheme as every open API answer.
x-kolaboro-key-idWhich key signed it. Public keys at /.well-known/kolaboro-keys.json.
x-kolaboro-timestampUnix seconds of this attempt. The same value is inside the signed body as timestamp; trust the body.
x-kolaboro-event, x-kolaboro-event-idEvent type and id, copied from the body for routing.
x-kolaboro-delivery-attempt1 for the first attempt, then 2, 3 and so on.
{ "type": "project.changed", "id": "evt_…", // stable for this change: retries reuse it, so de-duplicate on it "created": "2026-11-04T10:37:02.118Z", "project": { "id": "BALI-014", "market": "Bali", "country": "Indonesia", "page": "https://kolaboro.com/p/BALI-014", "api": "https://kolaboro.com/api/open/projects/BALI-014" }, "changes": ["Checks public: 2 of 6 → 3 of 6"], "before": { "score": 2, "max": 6, "stage": "off-plan", "checks": { … }, … }, "after": { "score": 3, "max": 6, "stage": "off-plan", "checks": { … }, … }, "dataset": { "updated": "2026-11-04", "sha256": "…" }, "webhook_id": 12, // yours: reject events addressed to another webhook "attempt": 1, "timestamp": 1793786222 // reject if more than 5 minutes from your clock }

Events are computed once a day after the research agent's run, with the same change detection as the push alerts: public checks, deposit protection and permit status, stage, reported delay, independent confirmations, satellite and register status, and changes on the developer's own page. The first time we see a project we only record its state, so your first event comes on its first real change. A ping event is sent when you register and whenever you press "Send a ping".

Verify every delivery

Four checks, in this order: the signature over the raw body, the timestamp within 5 minutes, the webhook_id is yours, and the event id is new to you. Read the raw body before parsing JSON: re-serialising changes the bytes and breaks the signature.

JavaScript (Node 20+, Deno, Bun, Workers, browsers)

verify.mjs, WebCrypto only, no dependencies.

Loading…
// Express app.post('/kolaboro', express.text({ type: '*/*' }), async (req, res) => { try { const evt = await verifyKolaboroWebhook(req.body, req.headers, { webhookId: 12, seen }); res.sendStatus(204); handle(evt); } catch (e) { res.status(400).send(e.message); } });

Python

verify.py, needs pip install cryptography.

Loading…
# Flask @app.post('/kolaboro') def kolaboro(): try: evt = verify_kolaboro_webhook(request.get_data(), request.headers, webhook_id=12, seen=seen) except Exception as e: return str(e), 400 handle(evt) return '', 204

Both files were tested against a local receiver: genuine deliveries pass, and an edited body, a replayed event, a stale timestamp and an event for another webhook are refused.

Retries and the delivery log

  • Up to 3 attempts during the run, 2 and 6 seconds apart, unless you answer with a 4xx other than 408 or 429 (those will not change on retry).
  • Still failing: the next daily runs try again, up to 6 attempts over 3 days, with a new timestamp and signature each time and the same event id.
  • After 20 failed deliveries in a row the webhook is switched off. Registering the same URL again switches it back on.
  • Every attempt is logged with status code, time taken and error. The holder of the key sees the last 100 below. We keep the event body only for deliveries that failed, so they can be retried.

Register, test and see deliveries

No key yet? Create one in a few seconds. The key is sent only in the x-api-key header and kept in this tab's memory. The URL must be public https on port 443, without an IP address or credentials. Up to 5 webhooks per key and 200 projects per webhook.

Protect my purchase