/webhooks/{id}/rotateGenerates a new signing secret for the endpoint and returns it.
There is no dual-secret grace window. The old secret stops being valid the instant this call commits. Any delivery already in flight was signed with the old secret and will fail verification at your receiver, and any retry of a previously-failed delivery is re-signed with the new one. Plan for a short window of rejected requests, or accept both secrets in your handler across the rollout.
The recommended sequence: deploy a receiver that accepts the old secret and a new one read from config, call this endpoint, write the returned secret to config, then drop the old one once lastSuccessAt is advancing again. If you cannot do that, rotate during a quiet period and expect the failures.
data.secret is returned exactly once, here. Reads only ever give you secretLastFour. Losing it means rotating again.
This returns 200, not 201 - nothing new is created, an existing endpoint is mutated.
A webhook.secret_rotated event is emitted, carrying only the id, name, secretLastFour and secretRolledAt, never the secret itself.
Refused for project-restricted keys.
Requires the webhooks:write scope.
curl --request POST \
--url 'https://api.envless.cloud/webhooks/<id>/rotate' \
--header 'Authorization: Bearer ev_sk_xxxxxxxxxxxx'https://api.envless.cloud/webhooks/{id}/rotateResponse 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.
Path parameters
idstringrequireduuidThe endpoint's
id(UUIDv7). No slug form.
Responses
200OK
Rotate a webhook signing secret succeeded.
successbooleanrequiredcodeintegerrequiredThe HTTP status of this response, repeated in the body.
messagestringrequirederrornullrequireddataobjectrequiredShow child attributesHide child attributes
idstringrequiredThe endpoint's id.
secretstringrequiredThe new plaintext signing secret, e.g.
ev_whsec_.... Returned only here; the previous secret is already dead.secretLastFourstring | nullrequiredLast four characters of the new secret, matching what reads will show from now on.
secretRolledAtstringrequireddate-timeWhen the rotation committed. Deliveries signed before this timestamp used the old secret.
_reqobjectShow child attributesHide child attributes
reqIdstringresTimeinteger | null
{
"success": true,
"code": 200,
"message": "OK",
"error": null,
"data": {
"id": "<string>",
"secret": "<string>",
"secretLastFour": "<string>",
"secretRolledAt": "2026-05-30T10:14:22.000Z"
},
"_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": 401,
"message": "Something went wrong.",
"error": {
"code": "auth.api_key_required",
"resource": "<string>",
"field": "<string>"
},
"data": {},
"_req": {
"reqId": "<string>",
"resTime": 1
}
}401Unauthorized
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_user_missing- the key is valid but its owner no longer exists.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.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.
404Not Found
webhook.not_found - no live endpoint with that id exists in this workspace.
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.