Webhook events
Every event Knock sends to your webhooks: the envelope and headers, then each event type with its fields and an example payload.
Knock POSTs an event to your webhooks when something happens in your account, such as a contact booking a meeting. Manage your webhooks with the /v1/webhooks endpoints: authenticate with your API key in the x-api-key header.
Every delivery is a JSON POST to your HTTPS URL with an envelope (id, type, apiVersion, occurredAt, requestId, data). Delivery is at least once: deduplicate on webhook-id, and do not rely on order. Answer 2xx within 5 seconds; Knock retries failures for up to 72 hours.
Knock does not sign deliveries. To check that a request comes from Knock, set a secret token: Knock sends it in the knock-webhook-token header.
The envelope
Every delivery is a JSON body with the same envelope, whatever the event type. Read type to know the shape of data.
{
"id": "0b4c7f9e-1d2a-4e3b-8c5d-6f7a8b9c0d1e",
"type": "meeting.booked",
"apiVersion": "2026-11-01",
"occurredAt": "2026-11-02T12:00:05.000Z",
"data": {
"meetingId": "2e4f6a8c-0b1d-4c3e-9f5a-7b8c9d0e1f2a",
"contactId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"status": "booked",
"bookingSource": "meeting_link",
"bookedAt": "2026-11-02T12:00:00.000Z",
"startAt": "2026-11-09T15:00:00.000Z",
"endAt": "2026-11-09T15:30:00.000Z",
"updatedAt": "2026-11-02T12:00:00.000Z",
"firstName": "Jane",
"lastName": "Doe",
"companyName": "Acme",
"companyDomain": "acme.com",
"segment": "Enterprise",
"ownerId": "4c3b2a19-0f8e-4d7c-9b6a-5e4d3c2b1a09",
"ownerName": "Alex Kim"
}
}| Field | Type | Present | Description |
|---|---|---|---|
id | string (uuid) | Always | ID of the event, also sent as the webhook-id header. It is the same on every retry, and on every webhook the event is sent to: use it to ignore an event you already processed. |
type | string | Always | The event type. |
apiVersion | string | Always | Version of this payload format. |
occurredAt | string (date-time) | Always | When the event happened. |
requestId | string (uuid) | Optional | The X-Request-Id of the API call that caused the event. Absent when no API call did (for example a booking made by a contact). |
data | object | Always | The event's details. Its fields depend on type, and are listed under each event below. |
Timestamps are ISO 8601 strings in UTC. apiVersion is the version of the payload format: every event type is 2026-11-01. Fields can be added within a version, so ignore the ones you don't know.
The headers
| Header | Sent | Description |
|---|---|---|
content-type | Always | application/json. |
user-agent | Always | Identifies Knock as the sender. |
webhook-id | Always | ID of the event, the same as the body's id. Use it to ignore an event you already processed: it is the same on every retry. |
webhook-timestamp | Always | When Knock sent this attempt, in Unix seconds. It changes on every retry. |
knock-webhook-id | Always | ID of the webhook this delivery is for (00000000-0000-0000-0000-000000000000 for a URL test, which has no webhook yet). |
knock-webhook-token | Only while the webhook has a secret token | The secret token you set on this webhook (secretToken on POST /v1/webhooks or PATCH /v1/webhooks/{webhookId}), as is. Sent only while the webhook has one; each webhook has its own. Knock reads it when it sends each attempt, so a new token applies from the next attempt, retries included. Knock does not sign deliveries: compare this header with the webhook's token (in constant time) and reject the request when it is missing or different. |
Header names are case-insensitive. The knock-webhook-token header is the only proof that a delivery came from Knock: compare it with the secret token you set on the webhook (see Verify webhooks). Answer with any 2xx within 5 seconds: Knock does not follow redirects and never reads the response body (see Delivery and retries).
Meetings
Events about the meetings contacts book with your team.
Meeting booked
meeting.bookedA contact booked a meeting with your team through Knock. data is the meeting as GET /v1/meetings returns it, at the time Knock sent the event. Test bookings and meetings between members of your own team are not sent, and nor is a meeting cancelled before Knock sent the event. occurredAt is when Knock announced the booking, which can be later than bookedAt when Knock held it back (for example while it qualified the contact).
user-agent*stringIdentifies Knock as the sender.
"Knock-Webhooks/1"webhook-id*string<uuid>ID of the event, the same as the body's id. Use it to ignore an event you already processed: it is the same on every retry.
webhook-timestamp*stringWhen Knock sent this attempt, in Unix seconds. It changes on every retry.
knock-webhook-id*string<uuid>ID of the webhook this delivery is for (00000000-0000-0000-0000-000000000000 for a URL test, which has no webhook yet).
knock-webhook-token?stringThe secret token you set on this webhook (secretToken on POST /v1/webhooks or PATCH /v1/webhooks/{webhookId}), as is. Sent only while the webhook has one; each webhook has its own. Knock reads it when it sends each attempt, so a new token applies from the next attempt, retries included. Knock does not sign deliveries: compare this header with the webhook's token (in constant time) and reject the request when it is missing or different.
application/jsonThe body of each meeting.booked delivery.
id*string<uuid>ID of the event, also sent as the webhook-id header. It is the same on every retry, and on every webhook the event is sent to: use it to ignore an event you already processed.
type*stringThe event type.
"meeting.booked"apiVersion*stringVersion of this payload format.
"2026-11-01"occurredAt*string<date-time>When the event happened.
requestId?string<uuid>The X-Request-Id of the API call that caused the event. Absent when no API call did (for example a booking made by a contact).
data*objectThe meeting, as GET /v1/meetings returns it.
meetingId*string<uuid>ID of the meeting.
contactId*string<uuid> | nullID of the contact who booked the meeting.
status*stringStatus of the meeting, at the time Knock sent the event:
booked: The meeting is booked. Always the status ofmeeting.bookedandmeeting.rescheduled.completed: The meeting time has passed (Knock does not know who attended). Always the status ofmeeting.completed.cancelled: The meeting was cancelled. Always the status ofmeeting.cancelled.
"booked""completed""cancelled"bookingSource*string | nullHow the contact booked:
messaging_app: Booked in a chat: the contact picked a time offered in the conversation, or a team member booked it for them from Slack.meeting_link: Booked through one of your Knock meeting links, which use your routing rules to pick the team member.personal_link: Booked through a team member's personal scheduling link, directly with that team member.
"messaging_app""meeting_link""personal_link"nullbookedAt*string<date-time> | nullWhen the meeting was booked.
startAt*string<date-time> | nullWhen the meeting starts.
endAt*string<date-time> | nullWhen the meeting ends.
updatedAt*string<date-time>When Knock last updated the meeting.
firstName*string | nullContact's first name.
lastName*string | nullContact's last name.
companyName*string | nullContact's company name.
companyDomain*string | nullContact's company domain.
segment*string | nullSegment the meeting was routed to.
ownerId*string<uuid> | nullID of the team member who hosts the meeting, or the contact's owner when Knock has no host for it.
ownerName*string | nullThat team member's name.
Delivered. Answer within 5 seconds, before you process the event: Knock waits no longer, and does not read the response body.
The body of each meeting.booked delivery.
{ "id": "0b4c7f9e-1d2a-4e3b-8c5d-6f7a8b9c0d1e", "type": "meeting.booked", "apiVersion": "2026-11-01", "occurredAt": "2026-11-02T12:00:05.000Z", "data": { "meetingId": "2e4f6a8c-0b1d-4c3e-9f5a-7b8c9d0e1f2a", "contactId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "status": "booked", "bookingSource": "meeting_link", "bookedAt": "2026-11-02T12:00:00.000Z", "startAt": "2026-11-09T15:00:00.000Z", "endAt": "2026-11-09T15:30:00.000Z", "updatedAt": "2026-11-02T12:00:00.000Z", "firstName": "Jane", "lastName": "Doe", "companyName": "Acme", "companyDomain": "acme.com", "segment": "Enterprise", "ownerId": "4c3b2a19-0f8e-4d7c-9b6a-5e4d3c2b1a09", "ownerName": "Alex Kim" }}{ "user-agent": "Knock-Webhooks/1", "webhook-id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "webhook-timestamp": "string", "knock-webhook-id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"}Meeting rescheduled
meeting.rescheduledA meeting booked through Knock moved to a new time through Knock. Every reschedule is a separate event with its own id. data is the meeting at its new time, plus previousStartAt and previousEndAt. A meeting moved directly in a calendar is not sent.
user-agent*stringIdentifies Knock as the sender.
"Knock-Webhooks/1"webhook-id*string<uuid>ID of the event, the same as the body's id. Use it to ignore an event you already processed: it is the same on every retry.
webhook-timestamp*stringWhen Knock sent this attempt, in Unix seconds. It changes on every retry.
knock-webhook-id*string<uuid>ID of the webhook this delivery is for (00000000-0000-0000-0000-000000000000 for a URL test, which has no webhook yet).
knock-webhook-token?stringThe secret token you set on this webhook (secretToken on POST /v1/webhooks or PATCH /v1/webhooks/{webhookId}), as is. Sent only while the webhook has one; each webhook has its own. Knock reads it when it sends each attempt, so a new token applies from the next attempt, retries included. Knock does not sign deliveries: compare this header with the webhook's token (in constant time) and reject the request when it is missing or different.
application/jsonThe body of each meeting.rescheduled delivery.
id*string<uuid>ID of the event, also sent as the webhook-id header. It is the same on every retry, and on every webhook the event is sent to: use it to ignore an event you already processed.
type*stringThe event type.
"meeting.rescheduled"apiVersion*stringVersion of this payload format.
"2026-11-01"occurredAt*string<date-time>When the event happened.
requestId?string<uuid>The X-Request-Id of the API call that caused the event. Absent when no API call did (for example a booking made by a contact).
data*objectThe meeting at its new time, and the time it had before.
meetingId*string<uuid>ID of the meeting.
contactId*string<uuid> | nullID of the contact who booked the meeting.
status*stringStatus of the meeting, at the time Knock sent the event:
booked: The meeting is booked. Always the status ofmeeting.bookedandmeeting.rescheduled.completed: The meeting time has passed (Knock does not know who attended). Always the status ofmeeting.completed.cancelled: The meeting was cancelled. Always the status ofmeeting.cancelled.
"booked""completed""cancelled"bookingSource*string | nullHow the contact booked:
messaging_app: Booked in a chat: the contact picked a time offered in the conversation, or a team member booked it for them from Slack.meeting_link: Booked through one of your Knock meeting links, which use your routing rules to pick the team member.personal_link: Booked through a team member's personal scheduling link, directly with that team member.
"messaging_app""meeting_link""personal_link"nullbookedAt*string<date-time> | nullWhen the meeting was booked.
startAt*string<date-time> | nullWhen the meeting starts.
endAt*string<date-time> | nullWhen the meeting ends.
updatedAt*string<date-time>When Knock last updated the meeting.
firstName*string | nullContact's first name.
lastName*string | nullContact's last name.
companyName*string | nullContact's company name.
companyDomain*string | nullContact's company domain.
segment*string | nullSegment the meeting was routed to.
ownerId*string<uuid> | nullID of the team member who hosts the meeting, or the contact's owner when Knock has no host for it.
ownerName*string | nullThat team member's name.
previousStartAt*string<date-time> | nullWhen the meeting started before this reschedule. Null when Knock does not know.
previousEndAt*string<date-time> | nullWhen the meeting ended before this reschedule. Null when Knock does not know.
Delivered. Answer within 5 seconds, before you process the event: Knock waits no longer, and does not read the response body.
The body of each meeting.rescheduled delivery.
{ "id": "0b4c7f9e-1d2a-4e3b-8c5d-6f7a8b9c0d1e", "type": "meeting.rescheduled", "apiVersion": "2026-11-01", "occurredAt": "2026-11-02T12:00:05.000Z", "data": { "meetingId": "2e4f6a8c-0b1d-4c3e-9f5a-7b8c9d0e1f2a", "contactId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "status": "booked", "bookingSource": "meeting_link", "bookedAt": "2026-11-02T12:00:00.000Z", "startAt": "2026-11-10T15:00:00.000Z", "endAt": "2026-11-10T15:30:00.000Z", "updatedAt": "2026-11-02T12:00:05.000Z", "firstName": "Jane", "lastName": "Doe", "companyName": "Acme", "companyDomain": "acme.com", "segment": "Enterprise", "ownerId": "4c3b2a19-0f8e-4d7c-9b6a-5e4d3c2b1a09", "ownerName": "Alex Kim", "previousStartAt": "2026-11-09T15:00:00.000Z", "previousEndAt": "2026-11-09T15:30:00.000Z" }}{ "user-agent": "Knock-Webhooks/1", "webhook-id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "webhook-timestamp": "string", "knock-webhook-id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"}Meeting declined
meeting.declinedA contact declined the calendar invitation of a meeting booked through Knock. data is the meeting as GET /v1/meetings returns it, at the time Knock sent the event, whatever its status, plus declinedByContactId: the contact who booked it or another guest from outside your team. Declines by members of your team are not sent, and nor are meetings between members of your own team. Every decline is a separate event with its own id; accepting again sends nothing. When every contact invited has declined, Knock also cancels the meeting and sends meeting.cancelled. occurredAt is when Knock read the decline from the host's calendar.
user-agent*stringIdentifies Knock as the sender.
"Knock-Webhooks/1"webhook-id*string<uuid>ID of the event, the same as the body's id. Use it to ignore an event you already processed: it is the same on every retry.
webhook-timestamp*stringWhen Knock sent this attempt, in Unix seconds. It changes on every retry.
knock-webhook-id*string<uuid>ID of the webhook this delivery is for (00000000-0000-0000-0000-000000000000 for a URL test, which has no webhook yet).
knock-webhook-token?stringThe secret token you set on this webhook (secretToken on POST /v1/webhooks or PATCH /v1/webhooks/{webhookId}), as is. Sent only while the webhook has one; each webhook has its own. Knock reads it when it sends each attempt, so a new token applies from the next attempt, retries included. Knock does not sign deliveries: compare this header with the webhook's token (in constant time) and reject the request when it is missing or different.
application/jsonThe body of each meeting.declined delivery.
id*string<uuid>ID of the event, also sent as the webhook-id header. It is the same on every retry, and on every webhook the event is sent to: use it to ignore an event you already processed.
type*stringThe event type.
"meeting.declined"apiVersion*stringVersion of this payload format.
"2026-11-01"occurredAt*string<date-time>When the event happened.
requestId?string<uuid>The X-Request-Id of the API call that caused the event. Absent when no API call did (for example a booking made by a contact).
data*objectThe meeting, and the contact who declined it.
meetingId*string<uuid>ID of the meeting.
contactId*string<uuid> | nullID of the contact who booked the meeting.
status*stringStatus of the meeting, at the time Knock sent the event:
booked: The meeting is booked. Always the status ofmeeting.bookedandmeeting.rescheduled.completed: The meeting time has passed (Knock does not know who attended). Always the status ofmeeting.completed.cancelled: The meeting was cancelled. Always the status ofmeeting.cancelled.
"booked""completed""cancelled"bookingSource*string | nullHow the contact booked:
messaging_app: Booked in a chat: the contact picked a time offered in the conversation, or a team member booked it for them from Slack.meeting_link: Booked through one of your Knock meeting links, which use your routing rules to pick the team member.personal_link: Booked through a team member's personal scheduling link, directly with that team member.
"messaging_app""meeting_link""personal_link"nullbookedAt*string<date-time> | nullWhen the meeting was booked.
startAt*string<date-time> | nullWhen the meeting starts.
endAt*string<date-time> | nullWhen the meeting ends.
updatedAt*string<date-time>When Knock last updated the meeting.
firstName*string | nullContact's first name.
lastName*string | nullContact's last name.
companyName*string | nullContact's company name.
companyDomain*string | nullContact's company domain.
segment*string | nullSegment the meeting was routed to.
ownerId*string<uuid> | nullID of the team member who hosts the meeting, or the contact's owner when Knock has no host for it.
ownerName*string | nullThat team member's name.
declinedByContactId*string<uuid>ID of the contact who declined: the contact who booked the meeting (contactId) or another guest from outside your team.
Delivered. Answer within 5 seconds, before you process the event: Knock waits no longer, and does not read the response body.
The body of each meeting.declined delivery.
{ "id": "0b4c7f9e-1d2a-4e3b-8c5d-6f7a8b9c0d1e", "type": "meeting.declined", "apiVersion": "2026-11-01", "occurredAt": "2026-11-02T12:00:05.000Z", "data": { "meetingId": "2e4f6a8c-0b1d-4c3e-9f5a-7b8c9d0e1f2a", "contactId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "status": "booked", "bookingSource": "meeting_link", "bookedAt": "2026-11-02T12:00:00.000Z", "startAt": "2026-11-09T15:00:00.000Z", "endAt": "2026-11-09T15:30:00.000Z", "updatedAt": "2026-11-02T12:00:00.000Z", "firstName": "Jane", "lastName": "Doe", "companyName": "Acme", "companyDomain": "acme.com", "segment": "Enterprise", "ownerId": "4c3b2a19-0f8e-4d7c-9b6a-5e4d3c2b1a09", "ownerName": "Alex Kim", "declinedByContactId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }}{ "user-agent": "Knock-Webhooks/1", "webhook-id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "webhook-timestamp": "string", "knock-webhook-id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"}Meeting cancelled
meeting.cancelledA meeting booked through Knock was cancelled, by the contact or by your team. data is the meeting as GET /v1/meetings returns it, with status cancelled, plus cancelledBy. Meetings between members of your own team are not sent, and nor is a cancellation undone before Knock sent the event. A booking cancelled before Knock announced it sends meeting.cancelled without meeting.booked. A meeting cancelled because every contact declined comes back, with no event, when one of them accepts again; cancelling it again is a separate event with its own id. occurredAt is when Knock recorded the cancellation.
user-agent*stringIdentifies Knock as the sender.
"Knock-Webhooks/1"webhook-id*string<uuid>ID of the event, the same as the body's id. Use it to ignore an event you already processed: it is the same on every retry.
webhook-timestamp*stringWhen Knock sent this attempt, in Unix seconds. It changes on every retry.
knock-webhook-id*string<uuid>ID of the webhook this delivery is for (00000000-0000-0000-0000-000000000000 for a URL test, which has no webhook yet).
knock-webhook-token?stringThe secret token you set on this webhook (secretToken on POST /v1/webhooks or PATCH /v1/webhooks/{webhookId}), as is. Sent only while the webhook has one; each webhook has its own. Knock reads it when it sends each attempt, so a new token applies from the next attempt, retries included. Knock does not sign deliveries: compare this header with the webhook's token (in constant time) and reject the request when it is missing or different.
application/jsonThe body of each meeting.cancelled delivery.
id*string<uuid>ID of the event, also sent as the webhook-id header. It is the same on every retry, and on every webhook the event is sent to: use it to ignore an event you already processed.
type*stringThe event type.
"meeting.cancelled"apiVersion*stringVersion of this payload format.
"2026-11-01"occurredAt*string<date-time>When the event happened.
requestId?string<uuid>The X-Request-Id of the API call that caused the event. Absent when no API call did (for example a booking made by a contact).
data*objectThe cancelled meeting, and who cancelled it.
meetingId*string<uuid>ID of the meeting.
contactId*string<uuid> | nullID of the contact who booked the meeting.
status*stringStatus of the meeting, at the time Knock sent the event:
booked: The meeting is booked. Always the status ofmeeting.bookedandmeeting.rescheduled.completed: The meeting time has passed (Knock does not know who attended). Always the status ofmeeting.completed.cancelled: The meeting was cancelled. Always the status ofmeeting.cancelled.
"booked""completed""cancelled"bookingSource*string | nullHow the contact booked:
messaging_app: Booked in a chat: the contact picked a time offered in the conversation, or a team member booked it for them from Slack.meeting_link: Booked through one of your Knock meeting links, which use your routing rules to pick the team member.personal_link: Booked through a team member's personal scheduling link, directly with that team member.
"messaging_app""meeting_link""personal_link"nullbookedAt*string<date-time> | nullWhen the meeting was booked.
startAt*string<date-time> | nullWhen the meeting starts.
endAt*string<date-time> | nullWhen the meeting ends.
updatedAt*string<date-time>When Knock last updated the meeting.
firstName*string | nullContact's first name.
lastName*string | nullContact's last name.
companyName*string | nullContact's company name.
companyDomain*string | nullContact's company domain.
segment*string | nullSegment the meeting was routed to.
ownerId*string<uuid> | nullID of the team member who hosts the meeting, or the contact's owner when Knock has no host for it.
ownerName*string | nullThat team member's name.
cancelledBy*stringWho cancelled the meeting:
contact: The contact cancelled the meeting, or every contact invited to it declined.team: A member of your team cancelled the meeting or deleted its calendar event, or every member of your team invited to it declined.
"contact""team"Delivered. Answer within 5 seconds, before you process the event: Knock waits no longer, and does not read the response body.
The body of each meeting.cancelled delivery.
{ "id": "0b4c7f9e-1d2a-4e3b-8c5d-6f7a8b9c0d1e", "type": "meeting.cancelled", "apiVersion": "2026-11-01", "occurredAt": "2026-11-02T12:00:05.000Z", "data": { "meetingId": "2e4f6a8c-0b1d-4c3e-9f5a-7b8c9d0e1f2a", "contactId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "status": "cancelled", "bookingSource": "meeting_link", "bookedAt": "2026-11-02T12:00:00.000Z", "startAt": "2026-11-09T15:00:00.000Z", "endAt": "2026-11-09T15:30:00.000Z", "updatedAt": "2026-11-02T12:00:05.000Z", "firstName": "Jane", "lastName": "Doe", "companyName": "Acme", "companyDomain": "acme.com", "segment": "Enterprise", "ownerId": "4c3b2a19-0f8e-4d7c-9b6a-5e4d3c2b1a09", "ownerName": "Alex Kim", "cancelledBy": "contact" }}{ "user-agent": "Knock-Webhooks/1", "webhook-id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "webhook-timestamp": "string", "knock-webhook-id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"}Meeting completed
meeting.completedA meeting booked through Knock ended. data is the meeting as GET /v1/meetings returns it, with status completed. Knock marks a meeting completed from the host's calendar once its end time has passed; it does not know who attended. A meeting whose host disconnected their calendar is never completed. Sent once per meeting. Meetings between members of your own team are not sent. occurredAt is when Knock marked the meeting completed, usually minutes after it ended.
user-agent*stringIdentifies Knock as the sender.
"Knock-Webhooks/1"webhook-id*string<uuid>ID of the event, the same as the body's id. Use it to ignore an event you already processed: it is the same on every retry.
webhook-timestamp*stringWhen Knock sent this attempt, in Unix seconds. It changes on every retry.
knock-webhook-id*string<uuid>ID of the webhook this delivery is for (00000000-0000-0000-0000-000000000000 for a URL test, which has no webhook yet).
knock-webhook-token?stringThe secret token you set on this webhook (secretToken on POST /v1/webhooks or PATCH /v1/webhooks/{webhookId}), as is. Sent only while the webhook has one; each webhook has its own. Knock reads it when it sends each attempt, so a new token applies from the next attempt, retries included. Knock does not sign deliveries: compare this header with the webhook's token (in constant time) and reject the request when it is missing or different.
application/jsonThe body of each meeting.completed delivery.
id*string<uuid>ID of the event, also sent as the webhook-id header. It is the same on every retry, and on every webhook the event is sent to: use it to ignore an event you already processed.
type*stringThe event type.
"meeting.completed"apiVersion*stringVersion of this payload format.
"2026-11-01"occurredAt*string<date-time>When the event happened.
requestId?string<uuid>The X-Request-Id of the API call that caused the event. Absent when no API call did (for example a booking made by a contact).
data*objectThe meeting, as GET /v1/meetings returns it.
meetingId*string<uuid>ID of the meeting.
contactId*string<uuid> | nullID of the contact who booked the meeting.
status*stringStatus of the meeting, at the time Knock sent the event:
booked: The meeting is booked. Always the status ofmeeting.bookedandmeeting.rescheduled.completed: The meeting time has passed (Knock does not know who attended). Always the status ofmeeting.completed.cancelled: The meeting was cancelled. Always the status ofmeeting.cancelled.
"booked""completed""cancelled"bookingSource*string | nullHow the contact booked:
messaging_app: Booked in a chat: the contact picked a time offered in the conversation, or a team member booked it for them from Slack.meeting_link: Booked through one of your Knock meeting links, which use your routing rules to pick the team member.personal_link: Booked through a team member's personal scheduling link, directly with that team member.
"messaging_app""meeting_link""personal_link"nullbookedAt*string<date-time> | nullWhen the meeting was booked.
startAt*string<date-time> | nullWhen the meeting starts.
endAt*string<date-time> | nullWhen the meeting ends.
updatedAt*string<date-time>When Knock last updated the meeting.
firstName*string | nullContact's first name.
lastName*string | nullContact's last name.
companyName*string | nullContact's company name.
companyDomain*string | nullContact's company domain.
segment*string | nullSegment the meeting was routed to.
ownerId*string<uuid> | nullID of the team member who hosts the meeting, or the contact's owner when Knock has no host for it.
ownerName*string | nullThat team member's name.
Delivered. Answer within 5 seconds, before you process the event: Knock waits no longer, and does not read the response body.
The body of each meeting.completed delivery.
{ "id": "0b4c7f9e-1d2a-4e3b-8c5d-6f7a8b9c0d1e", "type": "meeting.completed", "apiVersion": "2026-11-01", "occurredAt": "2026-11-02T12:00:05.000Z", "data": { "meetingId": "2e4f6a8c-0b1d-4c3e-9f5a-7b8c9d0e1f2a", "contactId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "status": "completed", "bookingSource": "meeting_link", "bookedAt": "2026-10-26T09:00:00.000Z", "startAt": "2026-11-02T11:30:00.000Z", "endAt": "2026-11-02T12:00:00.000Z", "updatedAt": "2026-11-02T12:00:05.000Z", "firstName": "Jane", "lastName": "Doe", "companyName": "Acme", "companyDomain": "acme.com", "segment": "Enterprise", "ownerId": "4c3b2a19-0f8e-4d7c-9b6a-5e4d3c2b1a09", "ownerName": "Alex Kim" }}{ "user-agent": "Knock-Webhooks/1", "webhook-id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "webhook-timestamp": "string", "knock-webhook-id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"}Meeting rejected
meeting.rejectedKnock did not let a contact book a meeting, because your routing rules disqualified them or they used a personal email. data is the contact, the email address they submitted and the reason. It carries no meeting: most contacts are turned away before they pick a time, and a time a contact had already picked is released. Knock can also disqualify a contact up to about ten minutes after they submitted their email, once it has enriched them. Every rejection is a separate event with its own id, so a contact turned away again on a later attempt sends another one. Test sessions and members of your own team are not sent, and nor is a rejection in a scheduling window your website loaded but the contact never opened. occurredAt is when Knock rejected the contact.
user-agent*stringIdentifies Knock as the sender.
"Knock-Webhooks/1"webhook-id*string<uuid>ID of the event, the same as the body's id. Use it to ignore an event you already processed: it is the same on every retry.
webhook-timestamp*stringWhen Knock sent this attempt, in Unix seconds. It changes on every retry.
knock-webhook-id*string<uuid>ID of the webhook this delivery is for (00000000-0000-0000-0000-000000000000 for a URL test, which has no webhook yet).
knock-webhook-token?stringThe secret token you set on this webhook (secretToken on POST /v1/webhooks or PATCH /v1/webhooks/{webhookId}), as is. Sent only while the webhook has one; each webhook has its own. Knock reads it when it sends each attempt, so a new token applies from the next attempt, retries included. Knock does not sign deliveries: compare this header with the webhook's token (in constant time) and reject the request when it is missing or different.
application/jsonThe body of each meeting.rejected delivery.
id*string<uuid>ID of the event, also sent as the webhook-id header. It is the same on every retry, and on every webhook the event is sent to: use it to ignore an event you already processed.
type*stringThe event type.
"meeting.rejected"apiVersion*stringVersion of this payload format.
"2026-11-01"occurredAt*string<date-time>When the event happened.
requestId?string<uuid>The X-Request-Id of the API call that caused the event. Absent when no API call did (for example a booking made by a contact).
data*objectThe contact Knock did not let book a meeting, and why.
contactId*string<uuid>ID of the contact.
email*string<email> | nullThe email address the contact submitted. Null when Knock does not know it.
reason*stringWhy Knock did not let the contact book:
disqualified: Your routing rules sent the contact to a segment that disqualifies them, either when they submitted their email or once Knock finished enriching them (a time they had already picked is then released).private_email: The contact submitted a personal email address (such as Gmail or Yahoo) on a meeting link that requires a work email.
"disqualified""private_email"Delivered. Answer within 5 seconds, before you process the event: Knock waits no longer, and does not read the response body.
The body of each meeting.rejected delivery.
{ "id": "0b4c7f9e-1d2a-4e3b-8c5d-6f7a8b9c0d1e", "type": "meeting.rejected", "apiVersion": "2026-11-01", "occurredAt": "2026-11-02T12:00:05.000Z", "data": { "contactId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "email": "jane@acme.com", "reason": "disqualified" }}{ "user-agent": "Knock-Webhooks/1", "webhook-id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "webhook-timestamp": "string", "knock-webhook-id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"}LinkedIn outreach
Events about the LinkedIn invitations Knock sends from your team members' accounts.
LinkedIn invitation sent
outreach.linkedin_invite_sentKnock sent a LinkedIn connection invitation to a contact from a team member's account. data names the contact, the team member whose account sent the invitation, why Knock sent it (source, plus meetingId when a booked meeting led to it), the contact's profile and the note sent with it. Every invitation is a separate event with its own id, so inviting the same contact again sends another one. Invitations team members send from LinkedIn themselves are not sent. Knock can hold an invitation back to respect LinkedIn's limits: it is sent when the invitation goes out, which can be days after the activity that led to it. occurredAt is when Knock sent the invitation.
user-agent*stringIdentifies Knock as the sender.
"Knock-Webhooks/1"webhook-id*string<uuid>ID of the event, the same as the body's id. Use it to ignore an event you already processed: it is the same on every retry.
webhook-timestamp*stringWhen Knock sent this attempt, in Unix seconds. It changes on every retry.
knock-webhook-id*string<uuid>ID of the webhook this delivery is for (00000000-0000-0000-0000-000000000000 for a URL test, which has no webhook yet).
knock-webhook-token?stringThe secret token you set on this webhook (secretToken on POST /v1/webhooks or PATCH /v1/webhooks/{webhookId}), as is. Sent only while the webhook has one; each webhook has its own. Knock reads it when it sends each attempt, so a new token applies from the next attempt, retries included. Knock does not sign deliveries: compare this header with the webhook's token (in constant time) and reject the request when it is missing or different.
application/jsonThe body of each outreach.linkedin_invite_sent delivery.
id*string<uuid>ID of the event, also sent as the webhook-id header. It is the same on every retry, and on every webhook the event is sent to: use it to ignore an event you already processed.
type*stringThe event type.
"outreach.linkedin_invite_sent"apiVersion*stringVersion of this payload format.
"2026-11-01"occurredAt*string<date-time>When the event happened.
requestId?string<uuid>The X-Request-Id of the API call that caused the event. Absent when no API call did (for example a booking made by a contact).
data*objectThe LinkedIn invitation Knock sent, the profile it went to and its note.
contactId*string<uuid>ID of the contact Knock invited.
memberId*string<uuid>ID of the team member whose LinkedIn account sent the invitation.
memberName*string | nullThat team member's name. Null when Knock has none.
meetingId*string<uuid> | nullID of the meeting that led Knock to invite the contact, as GET /v1/meetings lists it. Null when no booked meeting did.
source*string | nullWhy Knock invited the contact, null when Knock does not know:
meeting_link: A meeting booked through one of your Knock meeting links: the contact booked it or is a guest on it.personal_link: A meeting booked through a team member's personal scheduling link: the contact booked it or is a guest on it.qualified_not_booked: The contact qualified for a meeting on one of your Knock meeting links or a team member's personal scheduling link, but did not book a time.form: One of your form workflows picked the contact after they submitted a form.tag: One of your tag workflows picked the contact after Knock identified them on your website.product: One of your product workflows picked the contact from their activity in your product.
"meeting_link""personal_link""qualified_not_booked""form""tag""product"nulllinkedinProfileUrl*string | nullURL of the contact's LinkedIn profile the invitation went to. Null when Knock does not know it.
note*string | nullThe note sent with the invitation. Null when the invitation went without one (LinkedIn limits the notes a free account can send).
Delivered. Answer within 5 seconds, before you process the event: Knock waits no longer, and does not read the response body.
The body of each outreach.linkedin_invite_sent delivery.
{ "id": "0b4c7f9e-1d2a-4e3b-8c5d-6f7a8b9c0d1e", "type": "outreach.linkedin_invite_sent", "apiVersion": "2026-11-01", "occurredAt": "2026-11-02T12:00:05.000Z", "data": { "contactId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "memberId": "4c3b2a19-0f8e-4d7c-9b6a-5e4d3c2b1a09", "memberName": "Alex Kim", "meetingId": "2e4f6a8c-0b1d-4c3e-9f5a-7b8c9d0e1f2a", "source": "meeting_link", "linkedinProfileUrl": "https://www.linkedin.com/in/jane-doe", "note": "Hi Jane, looking forward to our meeting next week." }}{ "user-agent": "Knock-Webhooks/1", "webhook-id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "webhook-timestamp": "string", "knock-webhook-id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"}LinkedIn invitation accepted
outreach.linkedin_invite_acceptedA contact accepted a LinkedIn connection invitation Knock sent from a team member's account. data is the invitation as outreach.linkedin_invite_sent described it, without the profile and the note: the same contactId, memberId, meetingId and source. Sent once per invitation, often days after outreach.linkedin_invite_sent; deliveries can arrive in either order. Knock can miss an acceptance, so a missing event does not mean the contact declined. occurredAt is when Knock detected the acceptance, which can be later than when the contact accepted.
user-agent*stringIdentifies Knock as the sender.
"Knock-Webhooks/1"webhook-id*string<uuid>ID of the event, the same as the body's id. Use it to ignore an event you already processed: it is the same on every retry.
webhook-timestamp*stringWhen Knock sent this attempt, in Unix seconds. It changes on every retry.
knock-webhook-id*string<uuid>ID of the webhook this delivery is for (00000000-0000-0000-0000-000000000000 for a URL test, which has no webhook yet).
knock-webhook-token?stringThe secret token you set on this webhook (secretToken on POST /v1/webhooks or PATCH /v1/webhooks/{webhookId}), as is. Sent only while the webhook has one; each webhook has its own. Knock reads it when it sends each attempt, so a new token applies from the next attempt, retries included. Knock does not sign deliveries: compare this header with the webhook's token (in constant time) and reject the request when it is missing or different.
application/jsonThe body of each outreach.linkedin_invite_accepted delivery.
id*string<uuid>ID of the event, also sent as the webhook-id header. It is the same on every retry, and on every webhook the event is sent to: use it to ignore an event you already processed.
type*stringThe event type.
"outreach.linkedin_invite_accepted"apiVersion*stringVersion of this payload format.
"2026-11-01"occurredAt*string<date-time>When the event happened.
requestId?string<uuid>The X-Request-Id of the API call that caused the event. Absent when no API call did (for example a booking made by a contact).
data*objectA LinkedIn connection invitation Knock sent to a contact from a team member's account.
contactId*string<uuid>ID of the contact Knock invited.
memberId*string<uuid>ID of the team member whose LinkedIn account sent the invitation.
memberName*string | nullThat team member's name. Null when Knock has none.
meetingId*string<uuid> | nullID of the meeting that led Knock to invite the contact, as GET /v1/meetings lists it. Null when no booked meeting did.
source*string | nullWhy Knock invited the contact, null when Knock does not know:
meeting_link: A meeting booked through one of your Knock meeting links: the contact booked it or is a guest on it.personal_link: A meeting booked through a team member's personal scheduling link: the contact booked it or is a guest on it.qualified_not_booked: The contact qualified for a meeting on one of your Knock meeting links or a team member's personal scheduling link, but did not book a time.form: One of your form workflows picked the contact after they submitted a form.tag: One of your tag workflows picked the contact after Knock identified them on your website.product: One of your product workflows picked the contact from their activity in your product.
"meeting_link""personal_link""qualified_not_booked""form""tag""product"nullDelivered. Answer within 5 seconds, before you process the event: Knock waits no longer, and does not read the response body.
The body of each outreach.linkedin_invite_accepted delivery.
{ "id": "0b4c7f9e-1d2a-4e3b-8c5d-6f7a8b9c0d1e", "type": "outreach.linkedin_invite_accepted", "apiVersion": "2026-11-01", "occurredAt": "2026-11-02T12:00:05.000Z", "data": { "contactId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "memberId": "4c3b2a19-0f8e-4d7c-9b6a-5e4d3c2b1a09", "memberName": "Alex Kim", "meetingId": "2e4f6a8c-0b1d-4c3e-9f5a-7b8c9d0e1f2a", "source": "meeting_link" }}{ "user-agent": "Knock-Webhooks/1", "webhook-id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "webhook-timestamp": "string", "knock-webhook-id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"}Team
Events about the members of your team.
Team member LinkedIn status changed
member.linkedin_status_changedThe connection between a team member's LinkedIn account and Knock changed. data is the team member and the new status of their LinkedIn account: connected, disconnected (Knock lost access and the team member needs to reconnect it) or removed (the account was removed from Knock). Every change is a separate event with its own id. occurredAt is when Knock recorded the change: deliveries can arrive out of order, so keep the status with the latest occurredAt.
user-agent*stringIdentifies Knock as the sender.
"Knock-Webhooks/1"webhook-id*string<uuid>ID of the event, the same as the body's id. Use it to ignore an event you already processed: it is the same on every retry.
webhook-timestamp*stringWhen Knock sent this attempt, in Unix seconds. It changes on every retry.
knock-webhook-id*string<uuid>ID of the webhook this delivery is for (00000000-0000-0000-0000-000000000000 for a URL test, which has no webhook yet).
knock-webhook-token?stringThe secret token you set on this webhook (secretToken on POST /v1/webhooks or PATCH /v1/webhooks/{webhookId}), as is. Sent only while the webhook has one; each webhook has its own. Knock reads it when it sends each attempt, so a new token applies from the next attempt, retries included. Knock does not sign deliveries: compare this header with the webhook's token (in constant time) and reject the request when it is missing or different.
application/jsonThe body of each member.linkedin_status_changed delivery.
id*string<uuid>ID of the event, also sent as the webhook-id header. It is the same on every retry, and on every webhook the event is sent to: use it to ignore an event you already processed.
type*stringThe event type.
"member.linkedin_status_changed"apiVersion*stringVersion of this payload format.
"2026-11-01"occurredAt*string<date-time>When the event happened.
requestId?string<uuid>The X-Request-Id of the API call that caused the event. Absent when no API call did (for example a booking made by a contact).
data*objectThe team member, and the new status of their LinkedIn account's connection to Knock.
memberId*string<uuid>ID of the team member.
memberName*string | nullThe team member's name. Null when Knock has none.
status*stringThe new status of the team member's LinkedIn account:
connected: The account is connected to Knock: the team member connected it for the first time or reconnected it, or it recovered.disconnected: Knock lost access to the account, for example because LinkedIn signed it out: the team member needs to reconnect it.removed: The account was removed from Knock: the team member needs to connect it again.
"connected""disconnected""removed"Delivered. Answer within 5 seconds, before you process the event: Knock waits no longer, and does not read the response body.
The body of each member.linkedin_status_changed delivery.
{ "id": "0b4c7f9e-1d2a-4e3b-8c5d-6f7a8b9c0d1e", "type": "member.linkedin_status_changed", "apiVersion": "2026-11-01", "occurredAt": "2026-11-02T12:00:05.000Z", "data": { "memberId": "4c3b2a19-0f8e-4d7c-9b6a-5e4d3c2b1a09", "memberName": "Alex Kim", "status": "disconnected" }}{ "user-agent": "Knock-Webhooks/1", "webhook-id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "webhook-timestamp": "string", "knock-webhook-id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"}Enrichment
Events about your enrichment requests.
Enrichment request completed
enrichment_request.completedAn enrichment request you submitted finished. The event carries no results: fetch them with GET /v1/enrichment-requests/{enrichRequestId}. occurredAt is the request's completedAt. Deliveries can be delayed or fail, so keep a slow poll for requests whose event you never received.
user-agent*stringIdentifies Knock as the sender.
"Knock-Webhooks/1"webhook-id*string<uuid>ID of the event, the same as the body's id. Use it to ignore an event you already processed: it is the same on every retry.
webhook-timestamp*stringWhen Knock sent this attempt, in Unix seconds. It changes on every retry.
knock-webhook-id*string<uuid>ID of the webhook this delivery is for (00000000-0000-0000-0000-000000000000 for a URL test, which has no webhook yet).
knock-webhook-token?stringThe secret token you set on this webhook (secretToken on POST /v1/webhooks or PATCH /v1/webhooks/{webhookId}), as is. Sent only while the webhook has one; each webhook has its own. Knock reads it when it sends each attempt, so a new token applies from the next attempt, retries included. Knock does not sign deliveries: compare this header with the webhook's token (in constant time) and reject the request when it is missing or different.
application/jsonThe body of each enrichment_request.completed delivery.
id*string<uuid>ID of the event, also sent as the webhook-id header. It is the same on every retry, and on every webhook the event is sent to: use it to ignore an event you already processed.
type*stringThe event type.
"enrichment_request.completed"apiVersion*stringVersion of this payload format.
"2026-11-01"occurredAt*string<date-time>When the event happened.
requestId?string<uuid>The X-Request-Id of the API call that caused the event. Absent when no API call did (for example a booking made by a contact).
data*objectThe finished enrichment request, without its results.
enrichRequestId*string<uuid>ID of the enrichment request. Fetch the results with GET /v1/enrichment-requests/{enrichRequestId}.
type*stringWhether the request enriched people or companies.
"people""companies"status*stringHow the request ended:
completed: Every item was enriched or not found.partially_completed: Some items failed; the others were enriched or not found.failed: Every item failed.
"completed""partially_completed""failed"itemCounts*objectHow many items of the request ended in each state.
total*integerItems in the request.
completed*integerItems Knock enriched.
notFound*integerItems Knock found no data for.
failed*integerItems that failed.
createdAt*string<date-time>When you submitted the request.
completedAt*string<date-time>When the request finished.
Delivered. Answer within 5 seconds, before you process the event: Knock waits no longer, and does not read the response body.
The body of each enrichment_request.completed delivery.
{ "id": "0b4c7f9e-1d2a-4e3b-8c5d-6f7a8b9c0d1e", "type": "enrichment_request.completed", "apiVersion": "2026-11-01", "occurredAt": "2026-11-02T12:00:05.000Z", "data": { "enrichRequestId": "7d0f4f1e-2c3b-4a8e-9a51-0e6f3b8c2d14", "type": "people", "status": "completed", "itemCounts": { "total": 3, "completed": 2, "notFound": 1, "failed": 0 }, "createdAt": "2026-11-02T11:58:35.000Z", "completedAt": "2026-11-02T12:00:05.000Z" }}{ "user-agent": "Knock-Webhooks/1", "webhook-id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "webhook-timestamp": "string", "knock-webhook-id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"}Testing
The test event Knock sends when you ping a webhook or test a URL.
Webhook ping
webhook.pingA test event Knock sends when you ping a webhook or test a URL. It goes only to the webhook you pinged (POST /v1/webhooks/{webhookId}/ping), or to the URL you test before you create its webhook (POST /v1/webhooks/test). Answer it like any other event. A ping is never retried, and every ping is a new event with its own id.
user-agent*stringIdentifies Knock as the sender.
"Knock-Webhooks/1"webhook-id*string<uuid>ID of the event, the same as the body's id. Use it to ignore an event you already processed: it is the same on every retry.
webhook-timestamp*stringWhen Knock sent this attempt, in Unix seconds. It changes on every retry.
knock-webhook-id*string<uuid>ID of the webhook this delivery is for (00000000-0000-0000-0000-000000000000 for a URL test, which has no webhook yet).
knock-webhook-token?stringThe secret token you set on this webhook (secretToken on POST /v1/webhooks or PATCH /v1/webhooks/{webhookId}), as is. Sent only while the webhook has one; each webhook has its own. Knock reads it when it sends each attempt, so a new token applies from the next attempt, retries included. Knock does not sign deliveries: compare this header with the webhook's token (in constant time) and reject the request when it is missing or different.
application/jsonThe body of each webhook.ping delivery.
id*string<uuid>ID of the event, also sent as the webhook-id header. It is the same on every retry, and on every webhook the event is sent to: use it to ignore an event you already processed.
type*stringThe event type.
"webhook.ping"apiVersion*stringVersion of this payload format.
"2026-11-01"occurredAt*string<date-time>When the event happened.
requestId?string<uuid>The X-Request-Id of the API call that caused the event. Absent when no API call did (for example a booking made by a contact).
data*objectThe webhook that was pinged.
webhookId*string<uuid>ID of the webhook you pinged, or 00000000-0000-0000-0000-000000000000 when you tested a URL that has no webhook yet (POST /v1/webhooks/test).
Delivered. Answer within 5 seconds, before you process the event: Knock waits no longer, and does not read the response body.
The body of each webhook.ping delivery.
{ "id": "0b4c7f9e-1d2a-4e3b-8c5d-6f7a8b9c0d1e", "type": "webhook.ping", "apiVersion": "2026-11-01", "occurredAt": "2026-11-02T12:00:05.000Z", "data": { "webhookId": "5b1e2d3c-4f5a-4b6c-8d7e-9f0a1b2c3d4e" }}{ "user-agent": "Knock-Webhooks/1", "webhook-id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "webhook-timestamp": "string", "knock-webhook-id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"}