Skip to main content
For AI agents: a documentation index is available at https://docs.coverbase.com/llms.txt — this page is also available in markdown by appending .md to the URL.
The Webhooks API manages webhook subscription records and can send a manual test delivery to a registered endpoint. For the delivery format, fan-out, signature verification, and the event catalog, see the Webhooks guide. For the audit trail of every attempt, see Webhook delivery history. All endpoints are org-scoped to the API key. See API conventions for authentication and the error envelope.

Register a webhook

method
POST
POST /v1/webhooks
Creates a webhook subscription. Returns 201 Created.

Request body

url
string
required
The public HTTPS endpoint Coverbase will POST to. Rejected with 400 invalid_url if it is not HTTPS, carries credentials, uses a non-standard port, or resolves to a private / loopback / link-local / metadata address. See URL restrictions.
events
string[]
required
Event type strings to subscribe to. Strictly validated against the event catalog — any value not in the catalog fails with 422 invalid_event_types. Use the exact, case-sensitive values (e.g. Vendor.Created).
secret
string
required
Shared secret used to sign deliveries (HMAC-SHA256). Must be at least 16 characters. Write-only — it is never returned in any response.
description
string
Optional human-readable label.
enabled
boolean
Defaults to true. When false, the subscription is created in a disabled state.

Example request

curl -X POST "https://api.coverbase.app/v1/webhooks" \
  -H "Authorization: Bearer ak_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://hooks.example.com/coverbase",
    "events": ["Vendor.Created", "Vendor.Updated", "Assessment.Created"],
    "secret": "whsec_a1b2c3d4e5f6a7b8c9d0e1f2",
    "description": "Primary GRC integration"
  }'

Example response

201 Created:
{
  "id": "cbwh_d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9",
  "url": "https://hooks.example.com/coverbase",
  "events": ["Vendor.Created", "Vendor.Updated", "Assessment.Created"],
  "description": "Primary GRC integration",
  "enabled": true,
  "created_at": 1746576000,
  "last_delivery_status": null,
  "delivery_success_rate": null,
  "delivery_count": 0
}
The secret is never returned after creation. Store it at registration time. If lost, set a new one via the update endpoint.

Response fields

id
string
Webhook ID (cbwh_...).
url
string
The registered HTTPS endpoint.
events
string[]
Subscribed event strings.
description
string | null
The label, if set.
enabled
boolean
true when the subscription is active.
created_at
integer
Unix timestamp (seconds) when the webhook was created.
last_delivery_status
string | null
Status of the most recent recorded delivery attempt — one of delivered, timeout, error, skipped — or null if there are no recorded deliveries yet.
delivery_success_rate
number | null
Fraction of recorded attempts that were delivered, 01 (rounded to 4 dp). null when there are no recorded deliveries.
delivery_count
integer
Total recorded delivery attempts for this webhook. 0 when none.
See Webhook delivery history for the per-attempt records behind these aggregates.

Error responses

Validation is applied in this order: secret, then url, then events.
StatusBodyWhen
400{"detail": {"code": "weak_secret", "message": "Secret must be at least 16 characters."}}secret is shorter than 16 characters. Checked first.
400{"detail": {"code": "invalid_url", "message": "Webhook URL must be HTTPS and must not target a private, link-local, loopback, or metadata address."}}url is not a public HTTPS endpoint. See URL restrictions.
422{"detail": {"code": "invalid_event_types", "message": "Unsupported event type(s): ..."}}events contains a value not in the event catalog.
422Standard validation errorRequired fields missing or wrong type. See API conventions.

List webhooks

method
GET
GET /v1/webhooks
Returns all (non-archived) webhook subscriptions for the org, each including its delivery-health aggregates.

Example response

{
  "data": [
    {
      "id": "cbwh_d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9",
      "url": "https://hooks.example.com/coverbase",
      "events": ["Vendor.Created", "Assessment.Created"],
      "description": "Primary GRC integration",
      "enabled": true,
      "created_at": 1746576000,
      "last_delivery_status": "delivered",
      "delivery_success_rate": 0.9821,
      "delivery_count": 56
    }
  ]
}
Each item has the same shape as the create response, including last_delivery_status, delivery_success_rate, and delivery_count.

Update a webhook

method
PATCH
PATCH /v1/webhooks/{webhook_id}
Updates a webhook subscription. Only the fields you include are changed; omitted or null fields are left unchanged. Returns the updated webhook (same shape as the create response).

Request body

url
string
New endpoint. If provided, must start with https://.
events
string[]
Replace the subscribed event list. Strictly validated against the event catalog — an unsupported value fails with 422 invalid_event_types.
secret
string
Rotate the signing secret. If provided, must be at least 16 characters.
description
string
Update the label.
enabled
boolean
Enable or disable the subscription.

Example requests

Update events
curl -X PATCH "https://api.coverbase.app/v1/webhooks/cbwh_d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9" \
  -H "Authorization: Bearer ak_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "events": ["Vendor.Created", "Vendor.Updated", "Assessment.StatusChanged"] }'
Rotate the secret
curl -X PATCH "https://api.coverbase.app/v1/webhooks/cbwh_d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9" \
  -H "Authorization: Bearer ak_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "secret": "whsec_newsecretvalueatleast16" }'

Error responses

StatusBodyWhen
400{"detail": {"code": "weak_secret", "message": "..."}}A new secret shorter than 16 characters was supplied.
400{"detail": {"code": "invalid_url", "message": "Webhook URL must be HTTPS and must not target a private, link-local, loopback, or metadata address."}}A new url that is not a public HTTPS endpoint was supplied.
422{"detail": {"code": "invalid_event_types", "message": "Unsupported event type(s): ..."}}A new events value is not in the event catalog.
404{"detail": {"code": "webhook_not_found", "message": "Webhook not found."}}The webhook_id does not exist or is not in the API key’s org.

Delete a webhook

method
DELETE
DELETE /v1/webhooks/{webhook_id}
Archives (soft-deletes) the webhook subscription. Returns 204 No Content.
cURL
curl -X DELETE "https://api.coverbase.app/v1/webhooks/cbwh_d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9" \
  -H "Authorization: Bearer ak_live_xxx"
StatusBodyWhen
204(empty)Archived successfully.
404{"detail": {"code": "webhook_not_found", "message": "Webhook not found."}}The webhook_id does not exist or is not in the API key’s org.

Test a webhook

method
POST
POST /v1/webhooks/{webhook_id}/test
Enqueues a test delivery to the webhook’s URL. The delivery is dispatched asynchronously, so the response confirms the test was enqueued — it does not contain the receiver’s HTTP status.

Request body

event_type
string
default:"webhook.test"
Event type string to send in the test delivery. Unlike subscription events, this is not validated against the catalog — any string is accepted, including webhook.test.
payload
object
Optional. The object delivered as the envelope’s data. Defaults to {"test": true}.

Example request

Custom payload
curl -X POST "https://api.coverbase.app/v1/webhooks/cbwh_d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9/test" \
  -H "Authorization: Bearer ak_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "event_type": "Vendor.Created", "payload": { "vendor_id": "cbvndr_demo" } }'
Default payload
curl -X POST "https://api.coverbase.app/v1/webhooks/cbwh_d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9/test" \
  -H "Authorization: Bearer ak_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "event_type": "webhook.test" }'

Example response

{
  "event_id": "cbevt_test_8b3c1a4d9e7f2c5b6a8d1e3f9c7b4a2e",
  "delivered": true,
  "response_status": null,
  "response_time_ms": null,
  "attempted_at": 1746576180
}
delivered reflects that the test was enqueued, not that the receiver accepted it. response_status and response_time_ms are always null on this response because delivery happens out of band. The actual attempt (and the receiver’s real status) is recorded in delivery history. The delivered envelope’s data is your payload (default {"test": true}).

Error responses

StatusBodyWhen
404{"detail": {"code": "webhook_not_found", "message": "Webhook not found."}}The webhook_id does not exist or is not in the API key’s org.

List delivery attempts

method
GET
GET /v1/webhooks/{webhook_id}/deliveries
Returns the paginated delivery history for a webhook, newest first. Full request/response schema, query parameters, status values, and pagination are documented on the Webhook delivery history page.
cURL
curl "https://api.coverbase.app/v1/webhooks/cbwh_d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9/deliveries?limit=50&offset=0" \
  -H "Authorization: Bearer ak_live_xxx"
StatusBodyWhen
404{"detail": {"code": "webhook_not_found", "message": "Webhook not found."}}The webhook_id does not exist or is not in the API key’s org.

Appendix A: cURL quick reference

One block per endpoint, copy-paste ready. Set these once:
Setup
export CB="https://api.coverbase.app"       # or the sandbox host
export AK="ak_REPLACE_ME"                    # dashboard → API keys
export ORG_ID="cborg_REPLACE_ME"             # the id field on the org object
A=(-H "Authorization: Bearer $AK" -H "Content-Type: application/json")
Create a webhook
WH=$(curl -sS -X POST "$CB/v1/webhooks" "${A[@]}" -d '{
  "url": "https://hooks.example.com/coverbase",
  "events": ["Vendor.Created", "Assessment.StatusChanged"],
  "secret": "whsec_at_least_16_chars",
  "description": "Prod receiver",
  "enabled": true
}' | jq -r .id)
echo "$WH"
# 201 → {id, url, events, description, enabled, created_at,
#        last_delivery_status:null, delivery_success_rate:null, delivery_count:0}
Validation errors
# weak secret → 400 {"detail":{"code":"weak_secret",...}}
curl -sS -X POST "$CB/v1/webhooks" "${A[@]}" \
  -d '{"url":"https://hooks.example.com/x","events":["Vendor.Created"],"secret":"short"}'
# SSRF-blocked / non-HTTPS URL → 400 {"detail":{"code":"invalid_url",...}}
curl -sS -X POST "$CB/v1/webhooks" "${A[@]}" \
  -d '{"url":"https://169.254.169.254/latest/meta-data/","events":["Vendor.Created"],"secret":"whsec_at_least_16_chars"}'
curl -sS -X POST "$CB/v1/webhooks" "${A[@]}" \
  -d '{"url":"http://hooks.example.com/x","events":["Vendor.Created"],"secret":"whsec_at_least_16_chars"}'
# unsupported event type → 422 {"detail":{"code":"invalid_event_types",...}}
curl -sS -X POST "$CB/v1/webhooks" "${A[@]}" \
  -d '{"url":"https://hooks.example.com/x","events":["vendor.created"],"secret":"whsec_at_least_16_chars"}'
List webhooks (with delivery health)
curl -sS "$CB/v1/webhooks" "${A[@]}" | jq '.data[] |
  {id, url, enabled, last_delivery_status, delivery_success_rate, delivery_count}'
Update a webhook
curl -sS -X PATCH "$CB/v1/webhooks/$WH" "${A[@]}" \
  -d '{"events":["Vendor.Created","Vendor.Updated"],"enabled":false}'
# invalid event on update → 422 invalid_event_types
curl -sS -X PATCH "$CB/v1/webhooks/$WH" "${A[@]}" -d '{"events":["not.real"]}'
Send a test delivery
curl -sS -X POST "$CB/v1/webhooks/$WH/test" "${A[@]}" \
  -d '{"event_type":"Vendor.Created","payload":{"vendor_id":"cbvndr_demo"}}'
# default payload form:
curl -sS -X POST "$CB/v1/webhooks/$WH/test" "${A[@]}" -d '{"event_type":"webhook.test"}'
# → {event_id, delivered, response_status, response_time_ms, attempted_at}
Delivery history (paginated, newest first)
curl -sS "$CB/v1/webhooks/$WH/deliveries?limit=50&offset=0" "${A[@]}" | jq '{
  total, limit, offset,
  data: [.data[] | {event_type, status, response_status,
                     response_time_ms, error, attempted_at}]
}'
# next page:
curl -sS "$CB/v1/webhooks/$WH/deliveries?limit=50&offset=50" "${A[@]}"
# unknown id → 404 {"detail":{"code":"webhook_not_found",...}}
curl -sS "$CB/v1/webhooks/cbwh_nope/deliveries" "${A[@]}"
Delete a webhook
curl -sS -o /dev/null -w '%{http_code}\n' -X DELETE "$CB/v1/webhooks/$WH" "${A[@]}"  # 204
Org-level API IP allowlist
# Configure (CIDRs validated server-side; invalid → 422):
curl -sS -X POST "$CB/v1/org/$ORG_ID" "${A[@]}" \
  -d '{"api_ip_allowlist":["203.0.113.0/24","198.51.100.7/32"]}'
curl -sS -X POST "$CB/v1/org/$ORG_ID" "${A[@]}" \
  -d '{"api_ip_allowlist":["not-a-cidr"]}'        # → 422
# Read back (org object now returns api_ip_allowlist):
curl -sS "$CB/v1/org/$ORG_ID" "${A[@]}" | jq .api_ip_allowlist
# Once a non-empty allowlist is set, a call from an IP outside the CIDRs → 403:
#   {"detail":{"code":"ip_not_allowed","message":"..."}}
# Clear (unrestricted again):
curl -sS -X POST "$CB/v1/org/$ORG_ID" "${A[@]}" -d '{"api_ip_allowlist":[]}'
# Find the IP the API sees for the caller (same value the allowlist evaluates):
curl -sS "$CB/v1/whoami/ip" "${A[@]}"          # → {"client_ip":"203.0.113.42"}
For receiver-side signature verification (Python and Node), see Webhooks → Signature verification.