Outbound Webhooks
The Developer API lets you subscribe to events that happen inside Kinderpedia so your external system can react in real-time. When a lead is created, a stage moves, a note is added — your endpoint gets POSTed within seconds.
Distinct from the inbound webhooks that Kinderpedia uses internally for things like DocuSign Connect, Meta, WeChat (those are to Kinderpedia from external services, not from Kinderpedia to your systems).
Subscribing
Open Developer → Webhooks tab → Add subscription.
Fields:
- Destination URL — your endpoint (must be HTTPS in production).
- Events — checkbox list of what to subscribe to.
- Secret — auto-generated. Used for HMAC signature validation. Shown once; if lost, you have to regenerate.
- Description (optional).
Click Create. From then on, every matching event POSTs to your URL.
Events available
Current event list:
| Event | When fired | Payload data |
|---|---|---|
lead.created | New lead added (form, manual, import, API) | Full lead object |
lead.updated | Lead fields modified | Lead object + changed_fields |
lead.archived | Lead archived (recoverable) | { lead_id, reason } |
lead.unarchived | Lead restored | { lead_id } |
lead.deleted | Lead permanently deleted | { lead_id } |
stage.transitioned | Lead moved between stages | { lead_id, from_stage, to_stage } |
note.created | Note added to a lead | Full note object + lead_id |
activity.created | Activity added | Full activity object + lead_id |
activity.completed | Activity marked complete | { lead_id, activity_id, completed_at } |
form.submitted | Public form submission lands | { lead_id, form_id, submission_data } |
More events ship continuously; the OpenAPI document at /api/v1/openapi.json is the canonical list.
Payload shape
Every payload follows the same envelope:
json
{
"id": "01H8…",
"event": "lead.created",
"occurred_at": "2026-05-12T14:32:18+00:00",
"kindergarten_id": 12,
"data": {
"id": 4321,
"student_name": "Anna Smith",
"parent_email": "[email protected]",
...
}
}| Field | Meaning |
|---|---|
id | Unique delivery ID. Use this to dedupe on your side. |
event | Event type (use this to route). |
occurred_at | ISO-8601 timestamp of the event. |
kindergarten_id | School the event belongs to. Always your school. |
data | Event-specific payload. |
HMAC signatures
Every POST carries:
X-Kinderpedia-Event: lead.created
X-Kinderpedia-Delivery: 01H8…
X-Kinderpedia-Signature: sha256=<hex>The signature is hmac_sha256(secret, raw_body) — your subscription's secret as the HMAC key, the raw request body as the message. Compare constant-time against the supplied signature.
Verifying (Node.js)
js
const crypto = require('crypto')
function verify(rawBody, signatureHeader, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signatureHeader)
)
}Verifying (Python)
python
import hmac, hashlib
def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
expected = 'sha256=' + hmac.new(
secret.encode(),
raw_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header)Always validate before processing. If you skip validation, anyone with knowledge of your URL can spoof events.
Delivery semantics
- At-least-once. Network flakes or your endpoint returning 5xx will be retried.
- Async retry queue — deliveries are queued and worked off in the background, so your endpoint being slow never blocks the in-app action that triggered the event.
- Retry schedule: 1 minute → 5 minutes → 25 minutes. Max 3 retries.
- Terminal codes: 2xx = success (no retry). 4xx = permanent failure (no retry; dropped to the dead-letter UI). 5xx + network errors = retry.
So if your endpoint is down for an hour, you may get a few duplicates after it comes back up. Use the delivery id to dedupe.
Acknowledgement
Just return 2xx. Body content doesn't matter. We treat any 200-299 as success.
If you have heavy work to do (database writes, AI processing, etc.), respond 200 immediately and queue the work. Webhook delivery has a 30s timeout — sustained delays will retry-storm.
Make.com / n8n / Zapier
Both Make.com and n8n have webhook trigger nodes that accept inbound POSTs. The flow:
- In Make.com / n8n: create a webhook trigger. Copy the auto-generated URL.
- In Kinderpedia Developer → Webhooks: paste the URL, pick events, save.
- Save and grab the secret (Kinderpedia shows it once).
- Configure Make/n8n to validate the secret on each call (most have built-in HMAC support).
Failures
Failures go to the Failed deliveries tab in Developer. See Dead-letter UI for management.
Per-school isolation
Subscriptions are scoped to your school. Events from other schools never POST to your endpoint — even if you somehow share your URL with another school, their subscription would need its own configuration to start delivering.
Disabling vs deleting
The subscriptions list has a toggle to disable a subscription (no deliveries fire, but the config is preserved) and a delete button (configuration removed entirely). Disable is the safer choice when temporarily pausing.
Workflow webhooks vs Developer webhooks
Note: the SPA's Workflow Builder also has a "webhook" action type — but those use a different table (external_connections) and a different dispatcher. They're for workflow-internal use, not for general subscribing.
Developer API webhooks (this page) fire on system events regardless of workflow. Workflow webhooks fire only when a specific workflow runs to a "send webhook" step.
You can use either or both, depending on need:
- "Sync every new lead to my CRM" → Developer webhook on
lead.created - "Send a custom notification to Slack when Stage X is reached on Pipeline Y" → Workflow webhook
Best practices
- Validate HMAC every time. No exceptions.
- Dedupe by
id. Store seen delivery IDs for 24h. - Respond fast (200 within seconds). Queue heavy work.
- Subscribe to the minimum events you need. Don't subscribe to everything "just in case."
- Use separate subscriptions for separate use cases. Easier to disable one without affecting the others.