Overview & events
The webhook events Kwill sends, with full example payloads for each.
Webhooks let you receive real-time HTTP callbacks when things happen in your Kwill workspace. Each delivery is a POST to your endpoint with a JSON body describing the event.
Register an endpoint via the dashboard (Settings → Developer → Webhooks) or with POST /webhooks. See the API reference for the full parameter list.
Common envelope
Every webhook payload shares the same outer shape:
{
"id": "evt_01hxyz123456",
"type": "invoice.paid",
"created_at": "2024-11-15T14:32:00Z",
"data": {
"object": { /* the resource, as of when the event happened */ }
}
}| Field | Description |
|---|---|
id | Unique event ID. Also sent as X-Kwill-Event-Id. Use for deduplication. |
type | The event type string. |
created_at | ISO 8601 timestamp of when the event happened. |
data.object | The resource that changed, with the same fields the API returns for it (Client list fields, Project, Invoice, Proposal). |
Events
client.created
Fires when a new client is created (via API or dashboard).
{
"id": "evt_3f2a8b1c4d5e6f70",
"type": "client.created",
"created_at": "2024-03-15T14:22:00Z",
"data": {
"object": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "Jane Smith",
"email": "jane@acme.example",
"company": "Acme Corp",
"created_at": "2024-03-15T14:22:00Z"
}
}
}project.created
Fires when a new project is created.
{
"id": "evt_3f2a8b1c4d5e6f70",
"type": "project.created",
"created_at": "2024-03-15T14:22:00Z",
"data": {
"object": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef0123456789",
"client_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "Brand Refresh",
"type": "fixed",
"status": "active",
"description": "Full brand identity refresh for Q2 launch.",
"budget": 12000,
"start_date": "2024-03-01",
"end_date": "2024-05-31",
"stages": [
{
"id": "stg-1",
"name": "Discovery",
"status": "done"
},
{
"id": "stg-2",
"name": "Design",
"status": "active"
}
],
"created_at": "2024-03-15T14:22:00Z",
"client": {
"name": "Jane Smith",
"email": "jane@acme.example",
"company": "Acme Corp"
}
}
}
}project.stage_changed
Fires when a project moves to a different stage. Alongside data.object (the project), it carries data.stage and data.previous_stage, each { id, name } or null when the project is not in any stage (stage is null once the last stage is finished or the project is completed).
{
"id": "evt_3f2a8b1c4d5e6f70",
"type": "project.stage_changed",
"created_at": "2024-03-15T14:22:00Z",
"data": {
"object": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef0123456789",
"client_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "Brand Refresh",
"type": "fixed",
"status": "active",
"description": "Full brand identity refresh for Q2 launch.",
"budget": 12000,
"start_date": "2024-03-01",
"end_date": "2024-05-31",
"stages": [
{
"id": "stg-1",
"name": "Discovery",
"status": "done"
},
{
"id": "stg-2",
"name": "Design",
"status": "active"
}
],
"created_at": "2024-03-15T14:22:00Z",
"client": {
"name": "Jane Smith",
"email": "jane@acme.example",
"company": "Acme Corp"
}
},
"stage": {
"id": "stg-2",
"name": "Design"
},
"previous_stage": {
"id": "stg-1",
"name": "Discovery"
}
}
}invoice.created
Fires when a new invoice is created (API or dashboard).
{
"id": "evt_3f2a8b1c4d5e6f70",
"type": "invoice.created",
"created_at": "2024-03-15T14:22:00Z",
"data": {
"object": {
"id": "b2c3d4e5-f6a7-8901-bcde-f01234567890",
"client_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"project_id": "a1b2c3d4-e5f6-7890-abcd-ef0123456789",
"invoice_number": "INV-0042",
"line_items": [
{
"id": "li-1",
"description": "Design work",
"quantity": 40,
"rate": 125,
"amount": 5000
}
],
"discount": 0,
"tax_rate": 0,
"currency": "USD",
"subtotal": 5000,
"total": 5000,
"due_date": "2024-04-15",
"note": null,
"status": "draft",
"paid_at": null,
"viewed_at": null,
"created_at": "2024-03-15T14:22:00Z",
"client": {
"name": "Jane Smith",
"email": "jane@acme.example"
}
}
}
}invoice.paid
Fires when the client pays an invoice via the client portal.
{
"id": "evt_3f2a8b1c4d5e6f70",
"type": "invoice.paid",
"created_at": "2024-03-15T14:22:00Z",
"data": {
"object": {
"id": "b2c3d4e5-f6a7-8901-bcde-f01234567890",
"client_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"project_id": "a1b2c3d4-e5f6-7890-abcd-ef0123456789",
"invoice_number": "INV-0042",
"line_items": [
{
"id": "li-1",
"description": "Design work",
"quantity": 40,
"rate": 125,
"amount": 5000
}
],
"discount": 0,
"tax_rate": 0,
"currency": "USD",
"subtotal": 5000,
"total": 5000,
"due_date": "2024-04-15",
"note": null,
"status": "paid",
"paid_at": "2024-03-15T14:22:00Z",
"viewed_at": null,
"created_at": "2024-03-15T14:22:00Z",
"client": {
"name": "Jane Smith",
"email": "jane@acme.example"
}
}
}
}proposal.signed
Fires when a client accepts (signs) a proposal.
{
"id": "evt_3f2a8b1c4d5e6f70",
"type": "proposal.signed",
"created_at": "2024-03-15T14:22:00Z",
"data": {
"object": {
"id": "c3d4e5f6-a7b8-9012-cdef-012345678901",
"client_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"project_id": null,
"title": "Website Redesign Proposal",
"scope": "Full redesign of the marketing site.",
"deliverables": [
"Wireframes",
"Design mockups",
"Handoff assets"
],
"line_items": [
{
"id": "li-1",
"description": "Design work",
"quantity": 40,
"rate": 125,
"amount": 5000
}
],
"discount": 0,
"tax_rate": 0,
"currency": "USD",
"total": 5000,
"start_date": "2024-04-01",
"expiry_date": "2024-04-30",
"status": "signed",
"viewed_at": "2024-03-16T09:00:00Z",
"signed_at": "2024-03-15T14:22:00Z",
"declined_at": null,
"created_at": "2024-03-15T14:22:00Z",
"client": {
"name": "Jane Smith",
"email": "jane@acme.example"
}
}
}
}proposal.declined
Fires when a client declines a proposal.
{
"id": "evt_3f2a8b1c4d5e6f70",
"type": "proposal.declined",
"created_at": "2024-03-15T14:22:00Z",
"data": {
"object": {
"id": "c3d4e5f6-a7b8-9012-cdef-012345678901",
"client_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"project_id": null,
"title": "Website Redesign Proposal",
"scope": "Full redesign of the marketing site.",
"deliverables": [
"Wireframes",
"Design mockups",
"Handoff assets"
],
"line_items": [
{
"id": "li-1",
"description": "Design work",
"quantity": 40,
"rate": 125,
"amount": 5000
}
],
"discount": 0,
"tax_rate": 0,
"currency": "USD",
"total": 5000,
"start_date": "2024-04-01",
"expiry_date": "2024-04-30",
"status": "declined",
"viewed_at": "2024-03-16T09:00:00Z",
"signed_at": null,
"declined_at": "2024-03-15T14:22:00Z",
"created_at": "2024-03-15T14:22:00Z",
"client": {
"name": "Jane Smith",
"email": "jane@acme.example"
}
}
}
}webhook.test
A synthetic event sent when you click Send test event in the dashboard or call POST /webhooks/{id}/test. Its data.object is empty — it does not reflect real data. Use it to verify your endpoint is reachable and your signature verification is working.
{
"id": "evt_3f2a8b1c4d5e6f70",
"type": "webhook.test",
"created_at": "2024-03-15T14:22:00Z",
"data": {
"object": {}
}
}Test deliveries get exactly one attempt — failure is final and does not count against the endpoint's failure streak.