Skip to content

Register an endpoint

POST/v1/webhooks

Register

Admins register endpoints in the app under Settings → API → Webhooks, or through the API with a key that has webhooks:write. The URL must be https on a public host name. A workspace has up to 10 endpoints; webhooks are on Pro, Business and Enterprise.

The answer carries the endpoint's secret (whsec_…) once, with secretAlreadyShown: false. A retry with the same Idempotency-Key answers the same endpoint with secret: null and secretAlreadyShown: true (see Idempotency). Admins can reveal the secret again in the app, and rotate it: the old secret keeps signing next to the new one for up to 24 hours.

requestPOST /v1/webhooks
curl -X POST https://api.dotwick.com/v1/webhooks \  -H "Authorization: Bearer sk_live_…" \  -H "Content-Type: application/json" \  -H "Idempotency-Key: fleet-dashboard-hook" \  -d '{    "url": "https://hooks.example.com/dotwick",    "events": [      "scan.created",      "code.updated",      "bulk.completed"    ],    "description": "Fleet dashboard"  }'
response201 Createdthe secret is returned once; a replay answers secret: null
{  "id": "whe_3k8m2q9x7c4v1b6n",  "url": "https://hooks.example.com/dotwick",  "events": [    "scan.created",    "code.updated",    "bulk.completed"  ],  "description": "Fleet dashboard",  "enabled": true,  "disabledReason": null,  "disabledAt": null,  "apiVersion": "2026-09-01",  "createdAt": "2026-09-12T14:03:51.000Z",  "updatedAt": "2026-09-12T14:03:51.000Z",  "lastEventType": null,  "lastStatusCode": null,  "lastDeliveryAt": null,  "lastSuccessAt": null,  "failureCount": 0,  "failingSince": null,  "previousSecretExpiresAt": null,  "secret": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw",  "secretAlreadyShown": false}

Send a test event

A webhook.test event goes to the endpoint whatever it subscribes to — even when it's turned off — so you can check your verification code.

requestPOST /v1/webhooks/whe_3k8m2q9x7c4v1b6n/test
curl -X POST https://api.dotwick.com/v1/webhooks/whe_3k8m2q9x7c4v1b6n/test \  -H "Authorization: Bearer sk_live_…" \  -H "Idempotency-Key: fleet-dashboard-test-1"
response202 Accepted
{  "id": "whd_7t2p9w4k1m8x3c6v",  "endpointId": "whe_3k8m2q9x7c4v1b6n",  "eventId": "evt_1q2w3e4r5t6y7u8i",  "eventType": "webhook.test",  "status": "pending",  "attempts": 0,  "lastStatusCode": null,  "lastError": null,  "lastAttemptAt": null,  "nextAttemptAt": "2026-09-12T14:03:51.000Z",  "createdAt": "2026-09-12T14:03:51.000Z",  "deliveredAt": null}

What a delivery looks like

A POST with the event as a JSON body and these headers. The body's id and the webhook-id header are the same event id, identical on every retry and redelivery: use it to ignore duplicates.

webhook-idThe event id (evt_…). The same on every retry: dedupe on it.
webhook-timestampUnix seconds of this attempt.
webhook-signaturev1,<base64 signature>, two space-separated during rotation.
dotwick-event-typeThe event type, so you can route before parsing.
dotwick-delivery-idThe delivery (whd_…), as shown in the delivery log.
dotwick-attempt1 for the first attempt, then 2, 3…
content-typeapplication/json
user-agentDotwick-Webhooks/1.0 (+https://dotwick.com/docs/webhooks)

Retries

Anything but a 2xx within 10 seconds is a failure, and so is a redirect: we don't follow them. We retry after 5 s, 1 min, 5 min, 30 min, 1 h, 2 h, 4 h, 8 h and 8 h — 10 attempts over about 24 hours, each wait ±10% — and honour a longer Retry-After on 429 and 503. Then the delivery is marked failed.

Failing endpoints

When deliveries to an endpoint have failed for 24 hours in a row, its creator and the workspace admins get an email. After 3 days of failures (and at least 10 failed attempts) the endpoint is turned off and they are emailed again. An endpoint answering 410 Gone is turned off at once.

While an endpoint is off, no new events are recorded for it. Fix it, send a test event, then turn it back on with PATCH /v1/webhooks/{id} and { "enabled": true }.

Redeliver and recover

Every attempt is logged for 30 days with the headers we sent and what your server answered (successful scan.created deliveries for 7 days). Send one delivery again with POST /v1/webhooks/{id}/deliveries/{deliveryId}/redeliver, or every failed delivery since a date with POST /v1/webhooks/{id}/recover (1,000 per call).

Deliveries are not ordered: two changes to one code can arrive in either order. Compare the event's createdAt, or fetch the object, when order matters.