Webhooks
Register an endpoint and Legalize signs and POSTs to it. Signatures are constant-time HMAC-SHA256, delivery is retried on failure, and the SDKs ship a one-line verifier.
Create an endpoint
list
or retrieve. Lose it and you have to rotate by
deleting and re-creating the endpoint.
Delivery format
Each delivery is a POST with these headers:
X-Legalize-Signature: v1=<hex_hmac_sha256>— signature overtimestamp + "." + raw_body, keyed by the endpoint secret. Multiplev1=…entries can be comma-joined.X-Legalize-Timestamp— Unix seconds at the moment we signed the payload.X-Legalize-Event— the event type (redundant with the body but handy for fast routing).Content-Type: application/json.
Verify in your handler
Use the raw request bytes. Re-serializing the JSON
changes whitespace and breaks the signature. Every framework has
an escape hatch for this (Express: express.raw(),
Flask: request.get_data(), FastAPI: await request.body()).
Event types
test.ping— synthetic event from the dashboard's "Send test event" button. Delivered immediately, and the only one that is.law.created— a law the corpus did not have before.law.updated— a law we already had was re-ingested because its file changed.law.repealed— the law leftin_force. The payload carries bothstatusandprevious_status, so you can tellrepealedfromexpired,annulledorpartially_repealed.reform.created— a reform record that was not in the history before, with its date, source id and subject.
An endpoint only receives events created after it was registered: subscribing today is not a request for last week's changes. Events are dropped if they cannot be delivered within 7 days.
Your SDK accepts any string — we may add event types in future releases; forward compatibility is intentional.
Retries, delivery receipts, replay
A delivery that fails (non-2xx from your server, a timeout, a TLS error)
is retried on the next dispatch run, up to 5 attempts, and is then marked
failed. Since dispatch runs with the daily sync, those
attempts are normally a day apart. List past deliveries via
webhooks.deliveries(endpoint_id) and retry one immediately
with webhooks.retry(endpoint_id, delivery_id).
What arrives
Every event has the same envelope. data is what differs by type.
The sha is the commit in the country repository, so you can read the
exact text the reform produced straight from
raw.githubusercontent.com/legalize-dev/legalize-{country}/<sha>/…
without asking us again. A law.* event carries
title, status and last_updated instead, plus
previous_status on a repeal.
The body of the law is never in the payload. Fetch it with the sha, or
from GET /api/v1/{country}/laws/{id}.
What the delivery guarantees are
- At least once, not exactly once. The same event can arrive
twice — a dispatch run that dies after your server answered, or a manual
retry. Deduplicate on the
idin the payload; it is stable across redeliveries of the same event. - No ordering guarantee. Events from one run are delivered in no
particular order, so a
law.createdand thereform.createdfor the same law can arrive either way round. Treat each event as a signal to re-read the law, not as a delta to apply in sequence. - A disabled endpoint does not accumulate. While every endpoint on your account is disabled, events are not recorded for you at all — disabling and re-enabling loses that interval rather than queueing it. Delete an endpoint you no longer want; disable one only while you are fixing it.
tolerance= in Python, tolerance option
in Node, WithTolerance(...) in Go).