Configure webhook endpoints to receive real-time events from AscendKit.
Webhooks
Receive real-time notifications on your server when supported events happen in AscendKit. Webhooks let your backend react immediately to user signups, logins, survey submissions, and email lifecycle updates without polling the API.
Use cases: sync new users to your CRM, send a Slack notification on login, update internal records when a survey is submitted, or mirror email delivery events into your own logs.
Every webhook request is signed with HMAC-SHA256 so your server can verify it came from AscendKit. See Python SDK — Webhook verification or the Next.js Integration for implementation examples.
Create a webhook
ascendkit webhook create \
--url https://yourapp.com/webhooks/ascendkit \
--events user.created,user.login
| Flag | Description |
|---|---|
--url | Your endpoint URL (must be HTTPS in production) |
--events | Comma-separated list of event types to subscribe to |
List webhooks
ascendkit webhook list
View a webhook
ascendkit webhook get whc_abc123
Update a webhook
ascendkit webhook update whc_abc123 \
--url https://yourapp.com/webhooks/v2 \
--events user.created,user.login
Test a webhook
Send a test event to verify your endpoint is working:
ascendkit webhook test whc_abc123
Delete a webhook
ascendkit webhook delete whc_abc123
Payload shape
Webhook requests are JSON. The canonical event field is event. eventType is also included as a compatibility alias.
{
"event": "user.created",
"eventType": "user.created",
"timestamp": "2026-05-04T12:00:00Z",
"projectId": "prj_abc123",
"environmentId": "env_abc123",
"data": {
"userId": "usr_abc123",
"email": "[email protected]"
}
}
The same event type is also sent in the X-AscendKit-Event header. Verify the HMAC signature before trusting the payload.
Available events
| Event | Description |
|---|---|
user.created | User signs up (any method) |
user.login | User logs in |
user.logout | User logs out |
survey.completed | User submits a survey response |
email.sent | Email queued or sent |
email.delivered | Email delivery confirmed |
email.bounced | Email bounced |
email.complained | Complaint received |
email.opened | Tracked email open |
email.clicked | Tracked email link click |
email.unsubscribed | User unsubscribed from campaign email |
Verifying a delivery
Every delivery carries an HMAC-SHA256 signature in the x-ascendkit-signature header. Verify it before parsing the body. An unverified endpoint is an unauthenticated write path into your systems.
Verify against the raw bytes we sent. The most common implementation bug is parsing JSON and re-encoding it before verification — that produces a different byte sequence and the signature will never match, regardless of how correct your secret is.
body = await request.body() # raw -- not request.json()
verify_webhook_signature(
secret=SECRET,
signature_header=request.headers.get("x-ascendkit-signature", ""),
payload=body.decode(),
)
Verification also enforces a five-minute timestamp tolerance to prevent replay, so significant server clock drift shows up as signature failures.
Delivery semantics
Delivery is at-least-once. A slow or failed acknowledgement causes a retry, so the same event can arrive more than once.
Make handlers idempotent by keying on the event ID. Never assume exactly-once delivery — no webhook provider offers it, and designing as though yours does produces duplicate side effects under exactly the conditions retries exist for.
Return 2xx quickly. If your handler does slow work before responding, deliveries time out and retry, and you get the duplicate you were trying to avoid. Acknowledge first, then queue the work.
What not to use webhooks for
Copying user records into another system. That second copy is the problem AscendKit exists to remove — auth, email, journeys, and surveys already read the same usr_ profile, so there is nothing to synchronise.
The useful consumers are outward-facing:
| Purpose | Failure impact | Recommended |
|---|---|---|
| Copy users into another system | Silent data drift | Avoid |
| Notify a channel or person | A missed message | Yes |
| Trigger provisioning | Retryable | Yes |
| Write an audit record | Retryable | Yes |
Sync webhooks must be reliable or your data diverges. Reaction webhooks can fail and retry without anything becoming inconsistent, because they are not the source of truth for anything.
Troubleshooting
Signature verification always fails
The payload was re-serialised before verification. Use the raw body. If that is already correct, check server clock accuracy against the five-minute replay window.
Deliveries stop after a period of failures
Endpoints that fail repeatedly are backed off to protect both sides. Fix the endpoint, then use webhook test to confirm it responds before expecting normal delivery to resume.
Events fire in development but not production
Webhooks are configured per environment. Check which environment is active with ascendkit environment show — see CLI Setup.
Related
- Python SDK —
verify_webhook_signatureand the rest of the surface - Journeys — react to lifecycle events without leaving the platform
- Surveys — survey submissions are a common webhook trigger
Choosing which events to subscribe to
Subscribe narrowly. An endpoint receiving every event has to filter in your code, and every event you do not act on is still a request your server must authenticate, parse, and acknowledge.
Event filtering is configured per endpoint, so a Slack notifier and a provisioning service can subscribe to different sets rather than sharing one firehose. Separate endpoints also fail independently — a broken provisioning handler does not back off your notification deliveries.