/webhooksRegisters an HTTPS endpoint and subscribes it to a set of events.
data.secret is returned exactly once, on this response. Reads only ever expose secretLastFour. If you lose it, the only way to get a working secret again is POST /webhooks/{id}/rotate, which invalidates every signature already in flight. Store it before you do anything else.
The secret is formatted ev_whsec_<base64url>. Deliveries are signed per the Standard Webhooks spec and carry webhook-id, webhook-timestamp and webhook-signature headers, plus Envless-Event (the event type) and Envless-Delivery (the delivery id). The HMAC key is the full secret string including the ev_whsec_ prefix - if your verification library expects a base64 secret, pass base64(utf8(secret)). Do not strip the prefix.
URL rules, all enforced: https only, at most 2048 characters, no embedded credentials, and the host must be publicly routable. Loopback, private IPv4 ranges, carrier-grade NAT, link-local and unique-local IPv6, localhost, cloud metadata hosts, and any host ending .internal, .local, .localdomain or .home.arpa are all rejected. Redirects are not followed - a 3xx from your receiver is recorded as a failed attempt, so register the final URL.
eventTypes must be a non-empty array drawn from GET /webhooks/event-types. Unknown names are rejected with 400 and the message names each offender.
Endpoints are created enabled: true. Delivery is at-least-once with up to 5 attempts, backing off 30s, 2m, 10m, 30m then 1h, with a 10 second request timeout; any 2xx counts as success. Make your handler idempotent on webhook-id.
A workspace is capped at 20 endpoints and at 500 simultaneously queued deliveries.
Refused for project-restricted keys.
Requires the webhooks:write scope.
curl --request POST \
--url 'https://api.envless.cloud/webhooks' \
--header 'Authorization: Bearer ev_sk_xxxxxxxxxxxx' \
--header 'Content-Type: application/json' \
--data '{
"name": "Billing",
"url": "https://example.com/hooks/envless",
"description": "<string>",
"eventTypes": [
"variable.created",
"variable.updated"
]
}'https://api.envless.cloud/webhooksResponse headers
Authorization
AuthorizationstringrequiredheaderBearer token: an Envless API key (
ev_sk_…), created in the dashboard or withPOST /keys. The key is bound to one workspace; every request runs inside it.
Request body
application/json · required
namestringrequiredmin length 1max length 64Label for the endpoint. Required, trimmed, at most 64 characters.
urlstringrequiredurimax length 2048Required. Must be
https, at most 2048 characters, free of embedded credentials, and resolve to a public host. Register the final URL - redirects are recorded as failures.descriptionstring | nullmax length 200Optional note, at most 200 characters. Empty string is stored as null.
eventTypesstring[]requiredmin items 1Non-empty array of event names from
GET /webhooks/event-types. Any unrecognised name rejects the whole request.
Responses
201Created
Create a webhook endpoint succeeded.
successbooleanrequiredcodeintegerrequiredThe HTTP status of this response, repeated in the body.
messagestringrequirederrornullrequireddataobjectrequiredShow child attributesHide child attributes
idstringrequirednamestring | nullurlstringrequireduridescriptionstring | nulleventTypesstring[]requiredenabledbooleanrequiredsecretLastFourstring | nullLast four characters of the signing secret. The full secret is returned only on create and rotate.
lastSuccessAtstring | nulldate-timelastFailureAtstring | nulldate-timecreatedAtstringrequireddate-timeupdatedAtstring | nulldate-timesecretstringrequiredThe plaintext signing secret, e.g.
ev_whsec_.... Returned on this response only. Reads expose justsecretLastFour; the only way to get a fresh one is a rotation, which breaks in-flight deliveries.
_reqobjectShow child attributesHide child attributes
reqIdstringresTimeinteger | null
{
"success": true,
"code": 201,
"message": "OK",
"error": null,
"data": {
"id": "<string>",
"name": "<string>",
"url": "https://example.com",
"description": "<string>",
"eventTypes": [
"<string>"
],
"enabled": true,
"secretLastFour": "<string>",
"lastSuccessAt": "2026-05-30T10:14:22.000Z",
"lastFailureAt": "2026-05-30T10:14:22.000Z",
"createdAt": "2026-05-30T10:14:22.000Z",
"updatedAt": "2026-05-30T10:14:22.000Z",
"secret": "<string>"
},
"_req": {
"reqId": "<string>",
"resTime": 1
}
}Error envelope
Every failure below returns the same shape with success: false; branch on error.code, never on message.
successbooleanrequiredcodeintegerrequiredThe HTTP status of this response, repeated in the body.
messagestringrequirederrorobjectrequiredMachine-readable failure detail. Null on success.
Show child attributesHide child attributes
codeenum<string>requiredStable error code. Branch on this, never on
message.Options:
"auth.api_key_required","auth.api_key_not_found","auth.api_key_user_missing","auth.api_key_owner_not_member","auth.scope_missing","auth.scope_ceiling_exceeded","auth.key_project_restricted","auth.product_restricted","auth.permission_denied","auth.self_mutation_forbidden","auth.step_up_required","auth.step_up_fresh_required","auth.cli_token_not_accepted","auth.two_factor_required","auth.workspace_two_factor_required","auth.two_factor_invalid","auth.two_factor_replayed","auth.two_factor_locked","auth.two_factor_not_enabled","auth.two_factor_already_enabled","request.body_must_be_json","request.body_invalid","request.body_too_large","request.rate_limited","request.invalid_pagination","workspace.not_found","workspace.slug_conflict","workspace.limit_reached","workspace.delete_last","workspace.delete_too_early","workspace.delete_has_projects","domain.not_configured","domain.not_found","domain.invalid","domain.taken","domain.attempt_limit","product.not_found","product.slug_conflict","product.locked","project.not_found","project.ambiguous_product","project.slug_conflict","environment.not_found","environment.slug_conflict","environment.expose_disabled","environment.expose_private_product","variable.not_found","variable.name_conflict","variable.ciphertext_invalid","variable.baseline_drifted","version.not_found","member.not_found","member.already_exists","member.self_invite","member.self_mutation","member.owner_immutable","member.owner_cannot_leave","member.seat_limit","invite.already_pending","role.owner_not_assignable","role.limit_reached","role.immutable","role.in_use","invite.not_found","invite.expired","role.not_found","role.name_conflict","key.not_found","key.limit_reached","webhook.not_found","webhook.delivery_not_found","plan.limit_reached","plan.upgrade_required","request.not_found","request.method_not_allowed","request.conflict","auth.unauthorized","auth.forbidden","request.invalid","server.error"resourcestringThe resource or scope the failure relates to.
fieldstringThe request field that caused the failure.
dataobject | nullrequiredNull on most failures. A plan limit carries
{ limit, current }._reqobjectShow child attributesHide child attributes
reqIdstringresTimeinteger | null
{
"success": false,
"code": 400,
"message": "Something went wrong.",
"error": {
"code": "auth.api_key_required",
"resource": "<string>",
"field": "<string>"
},
"data": {},
"_req": {
"reqId": "<string>",
"resTime": 1
}
}400Bad Request
request.body_must_be_json- the body is absent or is not parseable JSON.request.body_invalid-nameorurlis missing or not a string;eventTypesis absent, empty, or contains a name outside the catalogue (error.fieldiseventTypes); the URL is not https, is over 2048 characters, carries credentials, or points at a private, loopback, link-local, metadata or.internal/.local/.localdomain/.home.arpahost; ordescriptionexceeds 200 characters.
401Unauthorized
auth.api_key_user_missing- the owner of the calling key could not be resolved, so the endpoint has no creator to attribute.auth.api_key_required- theAuthorizationheader is missing, or its value does not start withev_sk_.auth.api_key_not_found- the key is unknown, inactive or revoked.auth.api_key_owner_not_member- the key's owner is no longer a member of the key's workspace.
403Forbidden
auth.key_project_restricted- the calling key is fenced to specific projects; webhooks are workspace-wide and have no project dimension.auth.workspace_two_factor_required- the workspace requires two-factor authentication and the key's owner has not turned it on.auth.scope_missing- the key lacks a scope this operation requires;error.resourcenames it.
409Conflict
request.conflict - the workspace already has 20 webhook endpoints.
413Payload Too Large
request.body_too_large - the request body is larger than 1 MiB (1,048,576 bytes).
500Internal Server Error
server.error - an unexpected error; retry with backoff.
503Service Unavailable
server.error - the API is in maintenance mode or is missing its configuration; retry with backoff.