Webhook endpoints
Everything Settings › API and webhooks › Webhooks does, over the API with the webhooks:manage scope. A key acting for one person also needs that person’s permission to manage webhooks.
Scopes: webhooks:manage to change.
The webhook endpoint object
idstringThe endpoint’s id.
namestringIts name.
urlstringWhere deliveries go. HTTPS, on the public internet.
eventsarray of stringThe events it gets, or ["*"] for every event except the Activity log.
statusstringDisabled endpoints get nothing. Workloom disables one after 20 failed attempts in a row. One of active, disabled.
disabled_reasonstring, nullableWhy it was turned off.
consecutive_failuresinteger, nullableFailed attempts since the last success.
last_success_attimestamp, nullableThe last delivery your server accepted.
last_failure_attimestamp, nullableThe last failed attempt.
secretstring, nullableThe signing secret. Only on create, rotate and GET …/secret; null everywhere else.
workloom_versionstringThe dated version its deliveries are shaped in, set when it was added. Change it with PATCH to move it to a later one.
created_attimestampWhen it was added.
{
"id": "66ff91a7e4b0a1b2c3d4ee01",
"name": "Revenue alerts",
"url": "https://example.com/hooks/workloom",
"events": [
"deal.won",
"deal.lost"
],
"status": "active",
"disabled_reason": null,
"consecutive_failures": 0,
"last_success_at": "2026-10-07T09:12:44.000Z",
"last_failure_at": null,
"secret": null,
"workloom_version": "2026-10-07",
"created_at": "2026-10-01T09:00:00.000Z"
}The delivery object
idstringThe delivery id, as sent in X-Workloom-Delivery. Every attempt at one delivery shares it.
event_typestringThe event.
event_idstring, nullableThe event, when it can be sent again.
attemptinteger, nullableWhich attempt this was, from 1.
okbooleanWhether your server answered with a 2xx.
status_codeinteger, nullableWhat your server answered. Null when it did not answer.
errorstring, nullableWhat went wrong, when something did.
duration_msinteger, nullableHow long it took.
created_attimestampWhen the attempt was made.
{
"id": "whd_3f1c0d9a2b7e44c1a8d6b5f0",
"event_type": "deal.won",
"event_id": "evt_66ff5d63e4b0a1b2c3d4aa01",
"attempt": 1,
"ok": true,
"status_code": 200,
"error": null,
"duration_ms": 142,
"created_at": "2026-10-07T09:12:44.000Z"
}Endpoints
Make an event happen (sandbox only)
In a developer sandbox (sandbox-api.useworkloom.com, a wl_sbx_ key) only: records an event nobody can cause through the API (a reply, an open, a click, a bounce, a meeting, a booking, a form, a call, a Tell, an unsubscribe) about one of your sandbox contacts, and every endpoint subscribed to it gets the delivery, signed and retried as always. The live API answers sandbox_only.
typestringRequiredWhich event. The ones the API can cause itself (contact.created, deal.won…) are made the real way.
contact_idstringRequiredThe contact it is about.
categorystring, nullablereply.received: what kind of reply (interested, meeting, not_interested…). Default interested.
urlstring, nullableemail.clicked, booking.made, form.submitted: the page.
answersobjectform.submitted: what they entered, by field name.
outcomestring, nullablecall.logged: the outcome. Default connected.
titlestring, nullablemeeting.*: the meeting’s title; tell.fired: what happened.
start_attimestampmeeting.*: when it starts. Default two days from now.
400invalid_request, unknown_fields, reason_required or unsupported_version.
401authentication_required or invalid_key.
403missing_scope, forbidden, seat_inactive or organisation_inactive.
409duplicate, conflict or idempotency_key_in_use.
429rate_limited. Wait Retry-After seconds.
500internal_error.
curl -X POST "https://api.useworkloom.com/api/public/v1/sandbox/events" \
-H "Authorization: Bearer $WORKLOOM_KEY"const res = await fetch("https://api.useworkloom.com/api/public/v1/sandbox/events", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.WORKLOOM_KEY}`
}
});
const data = await res.json();import os, requests
res = requests.post(
"https://api.useworkloom.com/api/public/v1/sandbox/events",
headers={"Authorization": f"Bearer {os.environ['WORKLOOM_KEY']}"},
)
data = res.json(){}List webhook endpoints
400invalid_request, unknown_fields, reason_required or unsupported_version.
401authentication_required or invalid_key.
403missing_scope, forbidden, seat_inactive or organisation_inactive.
429rate_limited. Wait Retry-After seconds.
500internal_error.
curl "https://api.useworkloom.com/api/public/v1/webhooks" \
-H "Authorization: Bearer $WORKLOOM_KEY"const res = await fetch("https://api.useworkloom.com/api/public/v1/webhooks", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.WORKLOOM_KEY}`
}
});
const data = await res.json();import os, requests
res = requests.get(
"https://api.useworkloom.com/api/public/v1/webhooks",
headers={"Authorization": f"Bearer {os.environ['WORKLOOM_KEY']}"},
)
data = res.json(){
"data": [
{
"id": "66ff91a7e4b0a1b2c3d4ee01",
"name": "Revenue alerts",
"url": "https://example.com/hooks/workloom",
"events": [
"deal.won",
"deal.lost"
],
"status": "active",
"disabled_reason": null,
"consecutive_failures": 0,
"last_success_at": "2026-10-07T09:12:44.000Z",
"last_failure_at": null,
"secret": null,
"workloom_version": "2026-10-07",
"created_at": "2026-10-01T09:00:00.000Z"
}
]
}Add a webhook endpoint
Adds an endpoint. The answer carries its signing secret, the only time it is sent unasked. The address must be HTTPS on the public internet.
Idempotency-KeystringAny unique string, up to 255 characters. Send the same key when you retry and the request runs once; the first answer comes back with Idempotent-Replayed: true. Kept 24 hours.
namestringRequiredA name you will recognise.
urlstringRequiredAn HTTPS address on the public internet that accepts POST.
eventsarray of stringEvent types from GET /events, or ["*"] for every event except the Activity log. Defaults to ["*"].
workloom_versionstringThe dated version to shape its deliveries in. Defaults to the latest.
400invalid_request, unknown_fields, reason_required or unsupported_version.
401authentication_required or invalid_key.
403missing_scope, forbidden, seat_inactive or organisation_inactive.
409duplicate, conflict or idempotency_key_in_use.
422idempotency_key_reused.
429rate_limited. Wait Retry-After seconds.
500internal_error.
curl -X POST "https://api.useworkloom.com/api/public/v1/webhooks" \
-H "Authorization: Bearer $WORKLOOM_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"name": "Revenue alerts",
"url": "https://example.com/hooks/workloom",
"events": [
"deal.won",
"deal.lost"
]
}'const res = await fetch("https://api.useworkloom.com/api/public/v1/webhooks", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.WORKLOOM_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID()
},
body: JSON.stringify({
"name": "Revenue alerts",
"url": "https://example.com/hooks/workloom",
"events": [
"deal.won",
"deal.lost"
]
})
});
const data = await res.json();import os, uuid, requests
res = requests.post(
"https://api.useworkloom.com/api/public/v1/webhooks",
headers={"Authorization": f"Bearer {os.environ['WORKLOOM_KEY']}", "Idempotency-Key": str(uuid.uuid4())},
json={
"name": "Revenue alerts",
"url": "https://example.com/hooks/workloom",
"events": [
"deal.won",
"deal.lost"
]
},
)
data = res.json(){
"data": {
"id": "66ff91a7e4b0a1b2c3d4ee01",
"name": "Revenue alerts",
"url": "https://example.com/hooks/workloom",
"events": [
"deal.won",
"deal.lost"
],
"status": "active",
"disabled_reason": null,
"consecutive_failures": 0,
"last_success_at": "2026-10-07T09:12:44.000Z",
"last_failure_at": null,
"secret": null,
"workloom_version": "2026-10-07",
"created_at": "2026-10-01T09:00:00.000Z"
},
"livemode": true
}Get a webhook endpoint
idstringRequiredThe endpoint’s id.
400invalid_request, unknown_fields, reason_required or unsupported_version.
401authentication_required or invalid_key.
403missing_scope, forbidden, seat_inactive or organisation_inactive.
404not_found.
429rate_limited. Wait Retry-After seconds.
500internal_error.
curl "https://api.useworkloom.com/api/public/v1/webhooks/66ff91a7e4b0a1b2c3d4ee01" \
-H "Authorization: Bearer $WORKLOOM_KEY"const res = await fetch("https://api.useworkloom.com/api/public/v1/webhooks/66ff91a7e4b0a1b2c3d4ee01", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.WORKLOOM_KEY}`
}
});
const data = await res.json();import os, requests
res = requests.get(
"https://api.useworkloom.com/api/public/v1/webhooks/66ff91a7e4b0a1b2c3d4ee01",
headers={"Authorization": f"Bearer {os.environ['WORKLOOM_KEY']}"},
)
data = res.json(){
"data": {
"id": "66ff91a7e4b0a1b2c3d4ee01",
"name": "Revenue alerts",
"url": "https://example.com/hooks/workloom",
"events": [
"deal.won",
"deal.lost"
],
"status": "active",
"disabled_reason": null,
"consecutive_failures": 0,
"last_success_at": "2026-10-07T09:12:44.000Z",
"last_failure_at": null,
"secret": null,
"workloom_version": "2026-10-07",
"created_at": "2026-10-01T09:00:00.000Z"
}
}Change a webhook endpoint
idstringRequiredThe endpoint’s id.
namestringA new name.
urlstringA new address.
eventsarray of stringReplaces the events.
statusstringTurn it off, or back on. Turning it on clears the failure count.
workloom_versionstringMove its deliveries to another dated version.
400invalid_request, unknown_fields, reason_required or unsupported_version.
401authentication_required or invalid_key.
403missing_scope, forbidden, seat_inactive or organisation_inactive.
404not_found.
409duplicate, conflict or idempotency_key_in_use.
429rate_limited. Wait Retry-After seconds.
500internal_error.
curl -X PATCH "https://api.useworkloom.com/api/public/v1/webhooks/66ff91a7e4b0a1b2c3d4ee01" \
-H "Authorization: Bearer $WORKLOOM_KEY" \
-H "Content-Type: application/json" \
-d '{
"events": [
"deal.won",
"deal.lost",
"contact.deleted"
]
}'const res = await fetch("https://api.useworkloom.com/api/public/v1/webhooks/66ff91a7e4b0a1b2c3d4ee01", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.WORKLOOM_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"events": [
"deal.won",
"deal.lost",
"contact.deleted"
]
})
});
const data = await res.json();import os, requests
res = requests.patch(
"https://api.useworkloom.com/api/public/v1/webhooks/66ff91a7e4b0a1b2c3d4ee01",
headers={"Authorization": f"Bearer {os.environ['WORKLOOM_KEY']}"},
json={
"events": [
"deal.won",
"deal.lost",
"contact.deleted"
]
},
)
data = res.json(){
"data": {
"id": "66ff91a7e4b0a1b2c3d4ee01",
"name": "Revenue alerts",
"url": "https://example.com/hooks/workloom",
"events": [
"deal.won",
"deal.lost"
],
"status": "active",
"disabled_reason": null,
"consecutive_failures": 0,
"last_success_at": "2026-10-07T09:12:44.000Z",
"last_failure_at": null,
"secret": null,
"workloom_version": "2026-10-07",
"created_at": "2026-10-01T09:00:00.000Z"
},
"livemode": true
}Delete a webhook endpoint
idstringRequiredThe endpoint’s id.
400invalid_request, unknown_fields, reason_required or unsupported_version.
401authentication_required or invalid_key.
403missing_scope, forbidden, seat_inactive or organisation_inactive.
404not_found.
409duplicate, conflict or idempotency_key_in_use.
429rate_limited. Wait Retry-After seconds.
500internal_error.
curl -X DELETE "https://api.useworkloom.com/api/public/v1/webhooks/66ff91a7e4b0a1b2c3d4ee01" \
-H "Authorization: Bearer $WORKLOOM_KEY"const res = await fetch("https://api.useworkloom.com/api/public/v1/webhooks/66ff91a7e4b0a1b2c3d4ee01", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.WORKLOOM_KEY}`
}
});
const data = await res.json();import os, requests
res = requests.delete(
"https://api.useworkloom.com/api/public/v1/webhooks/66ff91a7e4b0a1b2c3d4ee01",
headers={"Authorization": f"Bearer {os.environ['WORKLOOM_KEY']}"},
)
data = res.json(){}Rotate an endpoint’s secret
Makes a new signing secret and returns it. The old one stops at once, so update your server first or accept both for a moment.
idstringRequiredThe endpoint’s id.
400invalid_request, unknown_fields, reason_required or unsupported_version.
401authentication_required or invalid_key.
403missing_scope, forbidden, seat_inactive or organisation_inactive.
404not_found.
409duplicate, conflict or idempotency_key_in_use.
429rate_limited. Wait Retry-After seconds.
500internal_error.
curl -X POST "https://api.useworkloom.com/api/public/v1/webhooks/66ff91a7e4b0a1b2c3d4ee01/rotate-secret" \
-H "Authorization: Bearer $WORKLOOM_KEY"const res = await fetch("https://api.useworkloom.com/api/public/v1/webhooks/66ff91a7e4b0a1b2c3d4ee01/rotate-secret", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.WORKLOOM_KEY}`
}
});
const data = await res.json();import os, requests
res = requests.post(
"https://api.useworkloom.com/api/public/v1/webhooks/66ff91a7e4b0a1b2c3d4ee01/rotate-secret",
headers={"Authorization": f"Bearer {os.environ['WORKLOOM_KEY']}"},
)
data = res.json(){
"data": {
"id": "66ff91a7e4b0a1b2c3d4ee01",
"name": "Revenue alerts",
"url": "https://example.com/hooks/workloom",
"events": [
"deal.won",
"deal.lost"
],
"status": "active",
"disabled_reason": null,
"consecutive_failures": 0,
"last_success_at": "2026-10-07T09:12:44.000Z",
"last_failure_at": null,
"secret": null,
"workloom_version": "2026-10-07",
"created_at": "2026-10-01T09:00:00.000Z"
},
"livemode": true
}Show an endpoint’s secret
idstringRequiredThe endpoint’s id.
400invalid_request, unknown_fields, reason_required or unsupported_version.
401authentication_required or invalid_key.
403missing_scope, forbidden, seat_inactive or organisation_inactive.
404not_found.
429rate_limited. Wait Retry-After seconds.
500internal_error.
curl "https://api.useworkloom.com/api/public/v1/webhooks/66ff91a7e4b0a1b2c3d4ee01/secret" \
-H "Authorization: Bearer $WORKLOOM_KEY"const res = await fetch("https://api.useworkloom.com/api/public/v1/webhooks/66ff91a7e4b0a1b2c3d4ee01/secret", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.WORKLOOM_KEY}`
}
});
const data = await res.json();import os, requests
res = requests.get(
"https://api.useworkloom.com/api/public/v1/webhooks/66ff91a7e4b0a1b2c3d4ee01/secret",
headers={"Authorization": f"Bearer {os.environ['WORKLOOM_KEY']}"},
)
data = res.json(){
"data": {
"secret": "whsec_Lr8Tg2Vb6Nq4Kk9Wd1Zp7a3c"
}
}Send a test event
Sends webhook.test now, signed like any event, and answers with what your server answered.
idstringRequiredThe endpoint’s id.
400invalid_request, unknown_fields, reason_required or unsupported_version.
401authentication_required or invalid_key.
403missing_scope, forbidden, seat_inactive or organisation_inactive.
404not_found.
409duplicate, conflict or idempotency_key_in_use.
429rate_limited. Wait Retry-After seconds.
500internal_error.
curl -X POST "https://api.useworkloom.com/api/public/v1/webhooks/66ff91a7e4b0a1b2c3d4ee01/test" \
-H "Authorization: Bearer $WORKLOOM_KEY"const res = await fetch("https://api.useworkloom.com/api/public/v1/webhooks/66ff91a7e4b0a1b2c3d4ee01/test", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.WORKLOOM_KEY}`
}
});
const data = await res.json();import os, requests
res = requests.post(
"https://api.useworkloom.com/api/public/v1/webhooks/66ff91a7e4b0a1b2c3d4ee01/test",
headers={"Authorization": f"Bearer {os.environ['WORKLOOM_KEY']}"},
)
data = res.json(){
"data": {
"sent": true,
"ok": true,
"status_code": 200,
"error": null
},
"livemode": true
}List recent deliveries
The latest attempts to this endpoint, newest first, from the last 30 days.
idstringRequiredThe endpoint’s id.
limitintegerHow many, 1 to 200. Default 50.
400invalid_request, unknown_fields, reason_required or unsupported_version.
401authentication_required or invalid_key.
403missing_scope, forbidden, seat_inactive or organisation_inactive.
404not_found.
429rate_limited. Wait Retry-After seconds.
500internal_error.
curl "https://api.useworkloom.com/api/public/v1/webhooks/66ff91a7e4b0a1b2c3d4ee01/deliveries?limit=50" \
-H "Authorization: Bearer $WORKLOOM_KEY"const res = await fetch("https://api.useworkloom.com/api/public/v1/webhooks/66ff91a7e4b0a1b2c3d4ee01/deliveries?limit=50", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.WORKLOOM_KEY}`
}
});
const data = await res.json();import os, requests
res = requests.get(
"https://api.useworkloom.com/api/public/v1/webhooks/66ff91a7e4b0a1b2c3d4ee01/deliveries?limit=50",
headers={"Authorization": f"Bearer {os.environ['WORKLOOM_KEY']}"},
)
data = res.json(){
"data": [
{
"id": "whd_3f1c0d9a2b7e44c1a8d6b5f0",
"event_type": "deal.won",
"event_id": "evt_66ff5d63e4b0a1b2c3d4aa01",
"attempt": 1,
"ok": true,
"status_code": 200,
"error": null,
"duration_ms": 142,
"created_at": "2026-10-07T09:12:44.000Z"
}
]
}Send a delivery again
Sends one delivery again now, with the same id, reading the event as it is now. A test or an Activity log entry is sent once and cannot be resent.
idstringRequiredThe endpoint’s id.
delivery_idstringRequiredThe delivery’s id (whd_…).
400invalid_request, unknown_fields, reason_required or unsupported_version.
401authentication_required or invalid_key.
403missing_scope, forbidden, seat_inactive or organisation_inactive.
404not_found.
409duplicate, conflict or idempotency_key_in_use.
429rate_limited. Wait Retry-After seconds.
500internal_error.
curl -X POST "https://api.useworkloom.com/api/public/v1/webhooks/66ff91a7e4b0a1b2c3d4ee01/deliveries/whd_3f1c0d9a2b7e44c1a8d6b5f0/resend" \
-H "Authorization: Bearer $WORKLOOM_KEY"const res = await fetch("https://api.useworkloom.com/api/public/v1/webhooks/66ff91a7e4b0a1b2c3d4ee01/deliveries/whd_3f1c0d9a2b7e44c1a8d6b5f0/resend", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.WORKLOOM_KEY}`
}
});
const data = await res.json();import os, requests
res = requests.post(
"https://api.useworkloom.com/api/public/v1/webhooks/66ff91a7e4b0a1b2c3d4ee01/deliveries/whd_3f1c0d9a2b7e44c1a8d6b5f0/resend",
headers={"Authorization": f"Bearer {os.environ['WORKLOOM_KEY']}"},
)
data = res.json(){
"data": {
"sent": true,
"ok": true,
"status_code": 200,
"error": null
},
"livemode": true
}