Webhooks
Subscribe to change events on a property or company and Placepoint delivers them to your HTTPS endpoint as signed CloudEvents 1.0 webhooks.
Payload
{
"specversion": "1.0",
"type": "no.placepoint.property.owner_changed",
"source": "https://data.placepoint.no/subscriptions/sub_9f2c1a",
"id": "evt_01J9X2N0M3FE9P6KQ7HTAV2RBT",
"time": "2026-09-07T10:15:00Z",
"data": {
"country": "NO",
"cadastreId": "0301-208-15-0-0"
}
}
specversion: always"1.0".type: reverse-DNS, e.g.no.placepoint.property.owner_changed.source: the subscription that produced the event.id: dedupe key.time: RFC 3339 UTC.data: the changed resource.
Verifying the signature
Every delivery carries Placepoint-Signature: t=<unix-timestamp>,v1=<hex-hmac>, where v1 is the hex-encoded HMAC-SHA256 of <t>.<raw-body>, keyed on your subscription's secret. Reject a delivery whose t is more than 5 minutes from the current time, even if the signature verifies; that bounds how long a captured request can be replayed.
Node:
const crypto = require('crypto');
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const t = Number(parts.t);
if (Math.abs(Date.now() / 1000 - t) > 300) return false;
const signed = `${parts.t}.${rawBody}`;
const expected = crypto.createHmac('sha256', secret).update(signed).digest('hex');
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(parts.v1, 'hex');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Python:
import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
if abs(time.time() - int(parts["t"])) > 300:
return False
signed = f"{parts['t']}.{raw_body.decode()}".encode()
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])
Use the raw, unparsed request body; re-serializing before verifying changes the bytes and breaks the comparison. Always use a constant-time compare (timingSafeEqual, hmac.compare_digest), never ==.
Delivery
At-least-once, with exponential backoff on a non-2xx response for up to 24 hours. A receiver must be idempotent on id: a duplicate delivery is expected, not a bug. Return 2xx quickly and do the real work asynchronously.
Managing subscriptions
Requires the subscriptions.write scope.
curl -X POST https://data.placepoint.no/subscriptions \
-H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"target": { "kind": "PROPERTY", "country": "NO", "cadastreId": "0301-208-15-0-0" },
"events": ["OWNER_CHANGED", "TRANSACTION_REGISTERED"],
"webhookUrl": "https://example.com/webhooks/placepoint",
"secret": "whsec_REPLACE_WITH_32_RANDOM_CHARS"
}'
| Method | Path | Purpose |
|---|---|---|
POST | /subscriptions | Create a subscription. |
GET | /subscriptions | List your active and paused subscriptions. |
DELETE | /subscriptions/{subscriptionId} | Delete one; delivery stops immediately. |
Available event types: OWNER_CHANGED, TENANT_CHANGED, TRANSACTION_REGISTERED, BUILDING_PERMIT_CHANGED.