CloudrawAPI v0
Menu

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

read-only token

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).

    Example: tq7x2m9k4ab

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.

      One of: security, connector, device, access, jit, all.

    • 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
curl -sS -X GET "https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks" \
  -H "Authorization: Bearer $CLOUDRAW_TOKEN"
PowerShell
$headers = @{ Authorization = "Bearer $env:CLOUDRAW_TOKEN" }
Invoke-RestMethod -Method Get -Uri "https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks" -Headers $headers
Python
import 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())
Node.js
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());
Response 200 · application/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

full-access tokenidempotent

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).

    Example: tq7x2m9k4ab

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.

    Example: 6f1c2b9e-3d4a-4c1e-9b8f-0a7d5e2c1b3f

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.

    One of: security, connector, device, access, jit, all.

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
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"
  ]
}'
PowerShell
$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 $body
Python
import 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())
Node.js
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());
Response 201 · application/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}

full-access token

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).

    Example: tq7x2m9k4ab

  • webhook_idstringrequired

    Webhook id.

    Example: wh_8d2k0s9x1m

Request body application/json

  • eventsarray of string

    Replaces the subscription; at least one category.

    One of: security, connector, device, access, jit, all.

  • 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.

    One of: security, connector, device, access, jit, all.

  • 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
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
}'
PowerShell
$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 $body
Python
import 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())
Node.js
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());
Response 200 · application/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}

full-access token

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).

    Example: tq7x2m9k4ab

  • webhook_idstringrequired

    Webhook id.

    Example: wh_8d2k0s9x1m

Responses

202 Done (the task is already complete).

application/json

  • idstringrequired
  • tenant_idstring
  • statestringrequired

    One of: pending, running, done, failed.

  • 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
curl -sS -X DELETE "https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks/wh_8d2k0s9x1m" \
  -H "Authorization: Bearer $CLOUDRAW_TOKEN"
PowerShell
$headers = @{ Authorization = "Bearer $env:CLOUDRAW_TOKEN" }
Invoke-RestMethod -Method Delete -Uri "https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks/wh_8d2k0s9x1m" -Headers $headers
Python
import 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)
Node.js
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);
Response 202 · application/json
{
  "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

full-access tokenidempotent

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).

    Example: tq7x2m9k4ab

  • webhook_idstringrequired

    Webhook id.

    Example: wh_8d2k0s9x1m

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.

    Example: 6f1c2b9e-3d4a-4c1e-9b8f-0a7d5e2c1b3f

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
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)"
PowerShell
$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 $headers
Python
import 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())
Node.js
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());
Response 200 · application/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

full-access tokenrate limited

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).

    Example: tq7x2m9k4ab

  • webhook_idstringrequired

    Webhook id.

    Example: wh_8d2k0s9x1m

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
curl -sS -X POST "https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks/wh_8d2k0s9x1m/test" \
  -H "Authorization: Bearer $CLOUDRAW_TOKEN"
PowerShell
$headers = @{ Authorization = "Bearer $env:CLOUDRAW_TOKEN" }
Invoke-RestMethod -Method Post -Uri "https://cdapi.cloudraw.com/v0/tenants/tq7x2m9k4ab/webhooks/wh_8d2k0s9x1m/test" -Headers $headers
Python
import 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())
Node.js
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());
Response 200 · application/json
{
  "delivered": false,
  "last_delivery": {
    "at": "2026-10-13T09:24:11.000Z",
    "ok": false,
    "code": 401,
    "error": "HTTP 401",
    "test": true
  }
}