API reference
Webhooks
Signed HTTPS callbacks for alerts and events. See the Webhooks guide for signature verification.
List webhooks
GET/v0/tenants/{tenant_id}/webhooks
Returns your webhooks and the categories a webhook can subscribe to. Signing secrets are never returned (has_secret only). See the WebhookDelivery schema for what Cloudraw sends.
Token: read-only or full access · Created by: owner or security admin
Path parameters
- tenant_idstringrequired
Your workspace id (Settings → Workspace in the console; it is also in every console URL).
Responses
200 The webhooks.
application/json
- itemsarray of Webhookrequired
items fields
- idstringrequired
- urlstringrequired
Receiver URL (https). Not editable: delete and re-create to change it.
- eventsarray of stringrequired
Subscribed categories.
- activebooleanrequired
False while paused; paused webhooks receive nothing.
- has_secretbooleanrequired
A signing secret exists. The secret itself is never returned after creation or rotation.
- created_atstring (date-time)required
- last_deliveryobject | null
Outcome of the most recent delivery attempt; null before the first one.
last_delivery fields
- atstring (date-time)
- okboolean
- codeinteger
HTTP status of a test delivery (0 when unknown or for queued deliveries).
- errorstring | null
- testboolean
Present and true when this was a test event.
- eventsarray of stringrequired
The categories a webhook can subscribe to.
- next_pagenull
Always null: all webhooks are returned.
401 unauthorized invalid-token token-expired
unauthorized: No Authorization: Bearer crw_… header was sent.
invalid-token: The token is unknown or was revoked.
token-expired: The token is past its expiry date. Create a new one.
403 insufficient-role tenant-assertion-mismatch
insufficient-role: The token's role ceiling does not allow this call.
tenant-assertion-mismatch: The token belongs to another workspace than {tenant_id}.
404 tenant-not-found
tenant-not-found: No workspace with this id.
curl -sS -X GET "https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks" \
-H "Authorization: Bearer $CLOUDRAW_TOKEN"$headers = @{ Authorization = "Bearer $env:CLOUDRAW_TOKEN" }
Invoke-RestMethod -Method Get -Uri "https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks" -Headers $headersimport os, uuid, requests
r = requests.get(
"https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks",
headers={"Authorization": f"Bearer {os.environ['CLOUDRAW_TOKEN']}"},
timeout=30,
)
r.raise_for_status()
print(r.json())const res = await fetch("https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CLOUDRAW_TOKEN}`,
},
});
if (!res.ok) throw new Error((await res.json()).code);
console.log(await res.json());{
"items": [
{
"id": "wh_8d2k0s9x1m",
"url": "https://hooks.example.com/cloudraw",
"events": [
"security",
"connector"
],
"active": true,
"has_secret": true,
"created_at": "2026-10-13T09:24:11.000Z",
"last_delivery": null
}
],
"events": [
"security",
"connector",
"device",
"access",
"jit",
"all"
],
"next_page": null
}Create a webhook
POST/v0/tenants/{tenant_id}/webhooks
Registers an HTTPS endpoint that receives a signed JSON POST for each event in the subscribed categories (see the WebhookDelivery schema for the body and signature headers). The URL must be https://, a public host (no private, loopback or link-local addresses, no internal names), without credentials, on port 443 or another allowed port (8088, 8443). The answer carries the signing secret (whsec_…) once — store it; it cannot be read back, only rotated. The webhook starts active. Audited.
Token: full access · Created by: owner
Idempotency: send Idempotency-Key. The answer carries a secret shown once: a retry with the same key answers 409 already-issued instead of the secret.
Path parameters
- tenant_idstringrequired
Your workspace id (Settings → Workspace in the console; it is also in every console URL).
Headers
- Idempotency-Keystring
A unique value (e.g. a UUID) per logical operation. Retrying with the same key and body within 24 hours returns the first answer instead of doing the work twice.
Request body application/json
- urlstring (uri)required
Receiver URL, https only, up to 400 characters.
- eventsarray of stringrequired
At least one category (duplicates are removed).
all= every event.
Responses
201 The webhook, with its signing secret (shown once).
application/json
400 https-url-required invalid-webhook-url events-required unknown-event category-not-available-yet
https-url-required: url is missing or does not start with https://.
invalid-webhook-url: The URL is not allowed (private or internal host, credentials in the URL, port not allowed); detail says why.
events-required: events is missing or empty.
unknown-event: events contains a category that is not in the list.
category-not-available-yet: events contains audit or billing, which webhooks do not send yet.
401 unauthorized invalid-token token-expired
unauthorized: No Authorization: Bearer crw_… header was sent.
invalid-token: The token is unknown or was revoked.
token-expired: The token is past its expiry date. Create a new one.
403 insufficient-scope insufficient-role tenant-assertion-mismatch
insufficient-scope: The token is read-only and this call changes something.
insufficient-role: The token's role ceiling does not allow this call.
tenant-assertion-mismatch: The token belongs to another workspace than {tenant_id}.
404 tenant-not-found
tenant-not-found: No workspace with this id.
409 idempotency-key-reuse already-issued
idempotency-key-reuse: The same Idempotency-Key was used with a different body within 24 hours.
already-issued: This Idempotency-Key already created a secret; it is shown only once. Retry with a new key.
503 secret-storage-unavailable busy
secret-storage-unavailable: Secrets cannot be stored safely right now. Retry later.
busy: Cloudraw is switching servers or cannot save right now. Retry after Retry-After seconds.
curl -sS -X POST "https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks" \
-H "Authorization: Bearer $CLOUDRAW_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.example.com/cloudraw",
"events": [
"security",
"connector"
]
}'$body = @'
{
"url": "https://hooks.example.com/cloudraw",
"events": [
"security",
"connector"
]
}
'@
$headers = @{ Authorization = "Bearer $env:CLOUDRAW_TOKEN"; "Idempotency-Key" = [guid]::NewGuid().ToString() }
Invoke-RestMethod -Method Post -Uri "https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks" -Headers $headers -ContentType "application/json" -Body $bodyimport os, uuid, requests
r = requests.post(
"https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks",
headers={"Authorization": f"Bearer {os.environ['CLOUDRAW_TOKEN']}", "Idempotency-Key": str(uuid.uuid4())},
json={
"url": "https://hooks.example.com/cloudraw",
"events": [
"security",
"connector"
]
},
timeout=30,
)
r.raise_for_status()
print(r.json())const res = await fetch("https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CLOUDRAW_TOKEN}`,
"Idempotency-Key": crypto.randomUUID(),
"Content-Type": "application/json",
},
body: JSON.stringify({
"url": "https://hooks.example.com/cloudraw",
"events": [
"security",
"connector"
]
}),
});
if (!res.ok) throw new Error((await res.json()).code);
console.log(await res.json());{
"id": "wh_8d2k0s9x1m",
"url": "https://hooks.example.com/cloudraw",
"events": [
"security",
"connector"
],
"active": true,
"has_secret": true,
"created_at": "2026-10-13T09:24:11.000Z",
"last_delivery": null,
"secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}Update a webhook
PATCH/v0/tenants/{tenant_id}/webhooks/{webhook_id}
Changes the subscribed categories and/or pauses (active: false) or resumes the webhook. The URL cannot be changed: delete the webhook and create a new one, so a new destination always gets a new signing secret. Audited.
Token: full access · Created by: owner
Path parameters
- tenant_idstringrequired
Your workspace id (Settings → Workspace in the console; it is also in every console URL).
- webhook_idstringrequired
Webhook id.
Request body application/json
- eventsarray of string
Replaces the subscription; at least one category.
- activeboolean
False pauses deliveries.
Responses
200 The updated webhook.
application/json
- idstringrequired
- urlstringrequired
Receiver URL (https). Not editable: delete and re-create to change it.
- eventsarray of stringrequired
Subscribed categories.
- activebooleanrequired
False while paused; paused webhooks receive nothing.
- has_secretbooleanrequired
A signing secret exists. The secret itself is never returned after creation or rotation.
- created_atstring (date-time)required
- last_deliveryobject | null
Outcome of the most recent delivery attempt; null before the first one.
last_delivery fields
- atstring (date-time)
- okboolean
- codeinteger
HTTP status of a test delivery (0 when unknown or for queued deliveries).
- errorstring | null
- testboolean
Present and true when this was a test event.
400 url-is-not-editable events-required unknown-event category-not-available-yet active-must-be-boolean
url-is-not-editable: The body contains url.
events-required: events is present but empty or not a list.
unknown-event: events contains a category that is not in the list.
category-not-available-yet: events contains audit or billing, which webhooks do not send yet.
active-must-be-boolean: active is not true or false.
401 unauthorized invalid-token token-expired
unauthorized: No Authorization: Bearer crw_… header was sent.
invalid-token: The token is unknown or was revoked.
token-expired: The token is past its expiry date. Create a new one.
403 insufficient-scope insufficient-role tenant-assertion-mismatch
insufficient-scope: The token is read-only and this call changes something.
insufficient-role: The token's role ceiling does not allow this call.
tenant-assertion-mismatch: The token belongs to another workspace than {tenant_id}.
404 webhook-not-found tenant-not-found
webhook-not-found: No webhook with this id.
tenant-not-found: No workspace with this id.
503 busy
busy: Cloudraw is switching servers or cannot save right now. Retry after Retry-After seconds.
curl -sS -X PATCH "https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks/wh_8d2k0s9x1m" \
-H "Authorization: Bearer $CLOUDRAW_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"events": [
"security",
"connector",
"device"
],
"active": true
}'$body = @'
{
"events": [
"security",
"connector",
"device"
],
"active": true
}
'@
$headers = @{ Authorization = "Bearer $env:CLOUDRAW_TOKEN" }
Invoke-RestMethod -Method Patch -Uri "https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks/wh_8d2k0s9x1m" -Headers $headers -ContentType "application/json" -Body $bodyimport os, uuid, requests
r = requests.patch(
"https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks/wh_8d2k0s9x1m",
headers={"Authorization": f"Bearer {os.environ['CLOUDRAW_TOKEN']}"},
json={
"events": [
"security",
"connector",
"device"
],
"active": True
},
timeout=30,
)
r.raise_for_status()
print(r.json())const res = await fetch("https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks/wh_8d2k0s9x1m", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.CLOUDRAW_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"events": [
"security",
"connector",
"device"
],
"active": true
}),
});
if (!res.ok) throw new Error((await res.json()).code);
console.log(await res.json());{
"id": "wh_8d2k0s9x1m",
"url": "https://hooks.example.com/cloudraw",
"events": [
"security",
"connector",
"device"
],
"active": true,
"has_secret": true,
"created_at": "2026-10-13T09:24:11.000Z",
"last_delivery": null
}Delete a webhook
DELETE/v0/tenants/{tenant_id}/webhooks/{webhook_id}
Removes the webhook. Deliveries still queued for it stop at once. Audited.
Token: full access · Created by: owner
Path parameters
- tenant_idstringrequired
Your workspace id (Settings → Workspace in the console; it is also in every console URL).
- webhook_idstringrequired
Webhook id.
Responses
202 Done (the task is already complete).
application/json
- idstringrequired
- tenant_idstring
- statestringrequired
- result_uristring | null
- errorobject | string | null
Why it failed, or a short status note (e.g.
revoke_pending).
401 unauthorized invalid-token token-expired
unauthorized: No Authorization: Bearer crw_… header was sent.
invalid-token: The token is unknown or was revoked.
token-expired: The token is past its expiry date. Create a new one.
403 insufficient-scope insufficient-role tenant-assertion-mismatch
insufficient-scope: The token is read-only and this call changes something.
insufficient-role: The token's role ceiling does not allow this call.
tenant-assertion-mismatch: The token belongs to another workspace than {tenant_id}.
404 webhook-not-found tenant-not-found
webhook-not-found: No webhook with this id.
tenant-not-found: No workspace with this id.
503 busy
busy: Cloudraw is switching servers or cannot save right now. Retry after Retry-After seconds.
curl -sS -X DELETE "https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks/wh_8d2k0s9x1m" \
-H "Authorization: Bearer $CLOUDRAW_TOKEN"$headers = @{ Authorization = "Bearer $env:CLOUDRAW_TOKEN" }
Invoke-RestMethod -Method Delete -Uri "https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks/wh_8d2k0s9x1m" -Headers $headersimport os, uuid, requests
r = requests.delete(
"https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks/wh_8d2k0s9x1m",
headers={"Authorization": f"Bearer {os.environ['CLOUDRAW_TOKEN']}"},
timeout=30,
)
r.raise_for_status()
print(r.status_code)const res = await fetch("https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks/wh_8d2k0s9x1m", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.CLOUDRAW_TOKEN}`,
},
});
if (!res.ok) throw new Error((await res.json()).code);
console.log(res.status);{
"id": "task_8s2k0x9m1d",
"tenant_id": "tq7x2m9k4ab",
"state": "done",
"result_uri": null,
"error": null
}Rotate a webhook signing secret
POST/v0/tenants/{tenant_id}/webhooks/{webhook_id}/rotate-secret
Issues a new signing secret, returned once in this answer. The old secret stops signing immediately — every delivery after this call (including retries of queued events) is signed with the new one, so update your receiver right away. Audited.
Token: full access · Created by: owner
Idempotency: send Idempotency-Key. The answer carries a secret shown once: a retry with the same key answers 409 already-issued instead of the secret.
Path parameters
- tenant_idstringrequired
Your workspace id (Settings → Workspace in the console; it is also in every console URL).
- webhook_idstringrequired
Webhook id.
Headers
- Idempotency-Keystring
A unique value (e.g. a UUID) per logical operation. Retrying with the same key and body within 24 hours returns the first answer instead of doing the work twice.
Responses
200 The webhook with its new signing secret (shown once).
application/json
401 unauthorized invalid-token token-expired
unauthorized: No Authorization: Bearer crw_… header was sent.
invalid-token: The token is unknown or was revoked.
token-expired: The token is past its expiry date. Create a new one.
403 insufficient-scope insufficient-role tenant-assertion-mismatch
insufficient-scope: The token is read-only and this call changes something.
insufficient-role: The token's role ceiling does not allow this call.
tenant-assertion-mismatch: The token belongs to another workspace than {tenant_id}.
404 webhook-not-found tenant-not-found
webhook-not-found: No webhook with this id.
tenant-not-found: No workspace with this id.
409 idempotency-key-reuse already-issued
idempotency-key-reuse: The same Idempotency-Key was used with a different body within 24 hours.
already-issued: This Idempotency-Key already created a secret; it is shown only once. Retry with a new key.
503 secret-storage-unavailable busy
secret-storage-unavailable: Secrets cannot be stored safely right now. Retry later.
busy: Cloudraw is switching servers or cannot save right now. Retry after Retry-After seconds.
curl -sS -X POST "https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks/wh_8d2k0s9x1m/rotate-secret" \
-H "Authorization: Bearer $CLOUDRAW_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"$headers = @{ Authorization = "Bearer $env:CLOUDRAW_TOKEN"; "Idempotency-Key" = [guid]::NewGuid().ToString() }
Invoke-RestMethod -Method Post -Uri "https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks/wh_8d2k0s9x1m/rotate-secret" -Headers $headersimport os, uuid, requests
r = requests.post(
"https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks/wh_8d2k0s9x1m/rotate-secret",
headers={"Authorization": f"Bearer {os.environ['CLOUDRAW_TOKEN']}", "Idempotency-Key": str(uuid.uuid4())},
timeout=30,
)
r.raise_for_status()
print(r.json())const res = await fetch("https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks/wh_8d2k0s9x1m/rotate-secret", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CLOUDRAW_TOKEN}`,
"Idempotency-Key": crypto.randomUUID(),
},
});
if (!res.ok) throw new Error((await res.json()).code);
console.log(await res.json());{
"id": "wh_8d2k0s9x1m",
"url": "https://hooks.example.com/cloudraw",
"events": [
"security",
"connector"
],
"active": true,
"has_secret": true,
"created_at": "2026-10-13T09:24:11.000Z",
"last_delivery": null,
"secret": "whsec_yyyyyyyyyyyyyyyyyyyyyyyyyyyy"
}Send a test event
POST/v0/tenants/{tenant_id}/webhooks/{webhook_id}/test
Sends one real, signed test event (category system, action webhook.test) to the webhook now and reports your receiver's answer — no retries. Works on paused webhooks too. The result is saved as last_delivery. Audited.
Token: full access · Created by: owner
Rate limit: 10 test events per hour per webhook.
Path parameters
- tenant_idstringrequired
Your workspace id (Settings → Workspace in the console; it is also in every console URL).
- webhook_idstringrequired
Webhook id.
Responses
200 What the receiver answered.
application/json
- deliveredbooleanrequired
The receiver answered 2xx.
- last_deliveryobjectrequired
last_delivery fields
- atstring (date-time)required
- okbooleanrequired
- codeinteger
HTTP status your receiver answered (0 when it did not answer).
- errorstring | null
- testboolean
401 unauthorized invalid-token token-expired
unauthorized: No Authorization: Bearer crw_… header was sent.
invalid-token: The token is unknown or was revoked.
token-expired: The token is past its expiry date. Create a new one.
403 insufficient-scope insufficient-role tenant-assertion-mismatch
insufficient-scope: The token is read-only and this call changes something.
insufficient-role: The token's role ceiling does not allow this call.
tenant-assertion-mismatch: The token belongs to another workspace than {tenant_id}.
404 webhook-not-found tenant-not-found
webhook-not-found: No webhook with this id.
tenant-not-found: No workspace with this id.
429 test-rate-limited
test-rate-limited: More than 10 test events for this webhook this hour.
500 test-failed
test-failed: The test could not be sent; detail says why.
503 busy
busy: Cloudraw is switching servers or cannot save right now. Retry after Retry-After seconds.
curl -sS -X POST "https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks/wh_8d2k0s9x1m/test" \
-H "Authorization: Bearer $CLOUDRAW_TOKEN"$headers = @{ Authorization = "Bearer $env:CLOUDRAW_TOKEN" }
Invoke-RestMethod -Method Post -Uri "https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks/wh_8d2k0s9x1m/test" -Headers $headersimport os, uuid, requests
r = requests.post(
"https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks/wh_8d2k0s9x1m/test",
headers={"Authorization": f"Bearer {os.environ['CLOUDRAW_TOKEN']}"},
timeout=30,
)
r.raise_for_status()
print(r.json())const res = await fetch("https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks/wh_8d2k0s9x1m/test", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CLOUDRAW_TOKEN}`,
},
});
if (!res.ok) throw new Error((await res.json()).code);
console.log(await res.json());{
"delivered": false,
"last_delivery": {
"at": "2026-10-13T09:24:11.000Z",
"ok": false,
"code": 401,
"error": "HTTP 401",
"test": true
}
}