Webhooks¶
Webhooks deliver real-time events to your services whenever workflows, boards, or analytics change.
Subscription Methods¶
- Project-level – Configure under Project → Integrations → Webhooks.
- Organization-level – Apply to all projects within an organization.
- Extensions – Automatically subscribe via manifests.
Event Types¶
issue.createdissue.updatedissue.transitionedworkflow.syncedboard.generatedanalytics.metric_alertextension.installation
Payload Structure¶
{
"id": "evt_01HXX...",
"event": "issue.transitioned",
"occurred_at": "2025-04-05T12:34:56Z",
"project": {
"slug": "core-platform",
"name": "Core Platform"
},
"data": {
"issue": { "key": "CORE-42", "title": "Fix bug" },
"transition": { "from": "review", "to": "done", "performed_by": "alex@example.com" }
}
}
issue.created¶
{
"event": "issue.created",
"timestamp": "2026-04-01T12:00:00Z",
"organization_id": 42,
"issue_id": 456,
"project_id": 10,
"title": "GDPR Access Request - Jan Novak",
"status": "received",
"issue_type": "dsar",
"priority": "high",
"assignee_id": null,
"custom_fields": {
"requester_name": "Jan Novak",
"requester_email": "jan@example.com",
"request_type": "access"
},
"labels": []
}
issue.status_changed¶
{
"event": "issue.status_changed",
"timestamp": "2026-04-01T14:30:00Z",
"organization_id": 42,
"issue_id": 456,
"project_id": 10,
"title": "GDPR Access Request - Jan Novak",
"status": "verification",
"previous_status": "received",
"issue_type": "dsar",
"priority": "high",
"assignee_id": 7
}
comment.created¶
{
"event": "comment.created",
"timestamp": "2026-04-01T15:00:00Z",
"organization_id": 42,
"comment_id": 789,
"issue_id": 456,
"project_id": 10,
"author": {
"id": 7,
"name": "Eva Dvorakova",
"email": "eva@acme.cz"
},
"body": "Identity verification document received and validated."
}
Delivery & Retries¶
- Requests include
X-Kiket-Event,X-Kiket-Signature(HMAC SHA-256), andX-Kiket-Deliveryheaders. - Respond with
2xxwithin 10 seconds. Failures retry up to 9 times with exponential backoff. - Use the delivery log in the UI to replay events manually.
Security¶
- Verify signatures using the shared secret shown in the webhook configuration.
- Restrict receiver endpoints to known IP ranges if necessary.
- Rotate secrets regularly; Kiket keeps previous secrets active for 24 hours to avoid downtime.
Testing¶
- Use the “Send Test Event” button in the UI to validate endpoints.
- CLI:
kiket webhooks send --event issue.created --url https://localhost:3000/webhook.
Webhooks keep your external systems in sync with Kiket’s workflow engine.
Webhook Subscriptions API¶
For standalone products and external integrations — subscribe to events programmatically.
Create a subscription¶
curl -X POST https://kiket.dev/api/v1/webhook_subscriptions \
-H "Authorization: Bearer eyJ..." \
-H "Content-Type: application/json" \
-d ‘{
"webhook_subscription": {
"url": "https://myapp.com/webhooks",
"events": ["issue.created", "issue.status_changed"],
"description": "My app webhook"
}
}’
The response includes a secret (shown only on create and show) for verifying signatures.
Available events¶
issue.created, issue.updated, issue.deleted, issue.assigned, issue.status_changed, issue.priority_changed, comment.created, comment.updated, project.created, project.updated, workflow.state_changed, workflow.action_triggered, milestone.created, milestone.reached, user.joined, user.role_changed
Use ["*"] to subscribe to all events.
Verify signatures¶
Each delivery includes:
| Header | Value |
|---|---|
X-Kiket-Event |
Event type (e.g., issue.created) |
X-Kiket-Signature |
sha256=<HMAC-SHA256 hex digest> |
X-Kiket-Delivery |
Unique delivery ID |
Verify using your subscription’s secret:
expected = "sha256=" + OpenSSL::HMAC.hexdigest("SHA256", secret, request.body.read)
actual = request.headers["X-Kiket-Signature"]
Rack::Utils.secure_compare(expected, actual)
Manage subscriptions¶
GET /api/v1/webhook_subscriptions # list all
GET /api/v1/webhook_subscriptions/:id # show (includes secret)
PATCH /api/v1/webhook_subscriptions/:id # update (url, events, status)
DELETE /api/v1/webhook_subscriptions/:id # delete
Set status: "paused" to temporarily disable a subscription without deleting it. Subscriptions are auto-disabled after 10 consecutive delivery failures.