Create a webhook endpoint
POST
/v1/webhook-endpointsRegisters a new webhook endpoint in the credential's organization and mints its signing secret, returned EXACTLY ONCE in this response. The endpoint starts UNVERIFIED (`verified: false`) — call the `verify` action before relying on it; the worker only queues deliveries to enabled AND verified endpoints. The URL is validated at save time (https, port 443, no embedded credentials) — a disallowed URL answers 422 `webhook_url_rejected` and creates NO row. Supports the optional `Idempotency-Key` header; a replayed response includes the ORIGINAL plaintext secret again within the 24h replay window (same tradeoff as `createApiKey`).
Required scopes
webhooks:write
Availability
Pro & Team plans
Quota cost
1 API call
Idempotency
Optional Idempotency-Key
Request
Request body
| Field | Type | Description |
|---|---|---|
urlrequired | string | The https destination URL. Must be https, port 443 (explicit or default), and carry no `user:pass@` credentials — a URL failing this check answers 422 `webhook_url_rejected` with the specific reason and creates NO row.e.g. https://example.com/webhooks/getitdone |
event_types | "task.created" | "task.updated" | "task.archived"[] | Subset of event types to subscribe to. Omit or send an empty array to subscribe to every type. |
enabled | boolean | Defaults to true. |
▸ ▾ Raw request schema
{
"type": "object",
"properties": {
"url": {
"type": "string",
"minLength": 1,
"maxLength": 2048,
"description": "The https destination URL. Must be https, port 443 (explicit or default), and carry no `user:pass@` credentials — a URL failing this check answers 422 `webhook_url_rejected` with the specific reason and creates NO row.",
"example": "https://example.com/webhooks/getitdone"
},
"event_types": {
"description": "Subset of event types to subscribe to. Omit or send an empty array to subscribe to every type.",
"type": "array",
"items": {
"$ref": "#/components/schemas/WebhookEndpointEventType"
}
},
"enabled": {
"description": "Defaults to true.",
"type": "boolean"
}
},
"required": [
"url"
],
"description": "Fields for registering a new webhook endpoint."
}Response 201
The created endpoint, with its plaintext secret shown once.
Fields
| Field | Type | Description |
|---|---|---|
idrequired | string | Stable id of the endpoint. |
urlrequired | string | The https destination URL. |
enabledrequired | boolean | False when disabled — manually, or auto-disabled after repeated delivery failures (see `disabled_reason`). |
event_typesrequired | "task.created" | "task.updated" | "task.archived"[] | Subscribed event types. An EMPTY array means "every type". |
verifiedrequired | boolean | True once the `verify` action has proven this endpoint accepts a correctly-signed request. |
disabled_reasonrequired | string | null | Why the endpoint was auto-disabled, or null when never auto-disabled. |
created_atrequired | string (date-time) | ISO-8601 UTC. |
updated_atrequired | string (date-time) | ISO-8601 UTC. |
secretrequired | string | The signing secret (`whsec_…`) — shown EXACTLY ONCE. Store it now; it cannot be retrieved again, only rotated. Note: replaying this exact request within the 24h Idempotency-Key window returns this SAME secret again — treat a stored Idempotency-Key as equally sensitive to the secret itself.e.g. whsec_51f3...redacted |
▸ ▾ Raw response schema
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Stable id of the endpoint."
},
"url": {
"type": "string",
"description": "The https destination URL."
},
"enabled": {
"type": "boolean",
"description": "False when disabled — manually, or auto-disabled after repeated delivery failures (see `disabled_reason`)."
},
"event_types": {
"type": "array",
"items": {
"$ref": "#/components/schemas/WebhookEndpointEventType"
},
"description": "Subscribed event types. An EMPTY array means \"every type\"."
},
"verified": {
"type": "boolean",
"description": "True once the `verify` action has proven this endpoint accepts a correctly-signed request."
},
"disabled_reason": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Why the endpoint was auto-disabled, or null when never auto-disabled."
},
"created_at": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "ISO-8601 UTC."
},
"updated_at": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
"description": "ISO-8601 UTC."
},
"secret": {
"type": "string",
"description": "The signing secret (`whsec_…`) — shown EXACTLY ONCE. Store it now; it cannot be retrieved again, only rotated. Note: replaying this exact request within the 24h Idempotency-Key window returns this SAME secret again — treat a stored Idempotency-Key as equally sensitive to the secret itself.",
"example": "whsec_51f3...redacted"
}
},
"required": [
"id",
"url",
"enabled",
"event_types",
"verified",
"disabled_reason",
"created_at",
"updated_at",
"secret"
],
"additionalProperties": false,
"description": "A freshly registered endpoint, including its one-time signing secret. Starts UNVERIFIED — call the `verify` action to prove ownership before relying on deliveries."
}Example request
curl
curl -X POST https://app.nowgetitdone.com/v1/webhook-endpoints \
-H "Authorization: Bearer gid_YOUR_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/getitdone"
}'Possible errors
- 400
validation_failedRequest validation failed - 401
missing_credentialsMissing credentials - 401
invalid_api_keyInvalid API key - 401
invalid_tokenInvalid access token - 403
insufficient_scopeInsufficient scope - 403
feature_not_enabledFeature not enabled on this plan - 409
idempotency_in_progressA request with this Idempotency-Key is still in progress - 422
idempotency_key_reusedIdempotency-Key reused with a different request - 429
rate_limitedRate limit exceeded - 429
quota_exhaustedPlan quota exhausted - 422
webhook_url_rejectedWebhook endpoint URL rejected - 500
internal_errorInternal server error