---
title: "Regenerate CI webhook secret"
method: POST
path: "/api/v1/checks/{id}/ci/regenerate"
tags: ["Checks"]
---

# Regenerate CI webhook secret

`POST /api/v1/checks/{id}/ci/regenerate`

## Request

- Base URL: `https://app.lastping.dev`
- URL: `https://app.lastping.dev/api/v1/checks/{id}/ci/regenerate`
- Auth: API key in header `X-Api-Key`

Generates a new CI webhook secret for the check and invalidates the old one.
The new secret is returned **write-once** — copy it immediately; it cannot be
retrieved again.  If the check has no `ci_provider` set, returns 400.

**This breaks whatever is already configured.** The old secret stops
verifying the moment this returns, so every CI job still signing with
it fails its signature check until the new value is pasted in. To read
the secret a monitor already has without rotating it, use the LastPing
console, which can show an existing secret without rotating it.

**Requires `admin` scope** when called with an API key, the same tier as
the `/api/v1/api-keys` routes: this rotates a live credential, and the
write-scoped key most likely to call it is the one sitting in the very
pipeline it would break. A key of a lower tier is refused 403
`INSUFFICIENT_SCOPE` with `required_scope: admin`. No password is asked
of a key.

**Requires the account password** when called with a browser session
(the console): send `{"password": "..."}`. A session is not scope-tiered
(the session is the account owner), so the re-check is what establishes
that the person at the keyboard is the account holder rather than
whoever borrowed the tab. A wrong password and an account with no
password at all (signed up through Google or GitHub) both answer 403
`INVALID_PASSWORD`, with the same body and the same latency, so the
response never says which accounts have passwords.

**Throttled on the signed-in user's own id**, with the budget sign-in
allows one account: 10 attempts per 15 minutes, shared with the
console's own reveal action. Ten failed reveals therefore leave
a regenerate attempt refused 429 `TOO_MANY_ATTEMPTS` too: the thing
being limited is password attempts by this session, and two budgets of
ten would be one oracle of twenty. Over the limit the request is refused
BEFORE the password is read or compared, and a successful attempt does
not reset the window.

**CSRF-gated** for cookie callers: the `lp_csrf` cookie value must be
echoed in the `X-CSRF-Token` header, or the answer is 403
`CSRF_REQUIRED`. The response is never cached (`Cache-Control:
no-store`).

To create a CI binding, supply `ci_provider` (and optionally `ci_workflow`,
`ci_branch`) when calling `POST /api/v1/checks`.

## Request body

- object
  - `password` string, password, required: The signed-in user's own account password. Never logged.

## Response `200`

New CI secret generated.  Copy `ci_secret` now: it will not be returned again.  Sent with `Cache-Control: no-store`.

- object
  - `ci_secret` string, required: The newly generated HMAC secret. WRITE-ONCE — never returned again.
  - `ci_webhook_url` string, uri, required: The webhook URL to configure in your CI provider.

## Other responses

- `400`: id is not a valid UUID (`INVALID_UUID`), the check has no ci_provider set (not a CI monitor), or a session caller's body is not a JSON object with a password field (`INVALID_BODY`).
- `401`: Missing or invalid credentials.
- `403`: `INSUFFICIENT_SCOPE` (an API key below the `admin` tier; the problem body's `required_scope` names it), `INVALID_PASSWORD` (a session caller's wrong password, or an account with no password set) or `CSRF_REQUIRED` (the `X-CSRF-Token` header was missing or did not match the cookie). The body never carries a `ci_secret` key.
- `404`: Check not found in this project. Answered identically for a check that does not exist and one belonging to another project, and answered BEFORE the password is evaluated, so a foreign id can never confirm a password guess or spend the owner's attempt budget.
- `429`: Too many password attempts by this user (`TOO_MANY_ATTEMPTS`). The submitted password was not evaluated. The budget is shared with the console's own reveal action.

---

[API](https://skmtc.dev/lastping/apis/lastping-management-api.md) · [All operations](https://skmtc.dev/lastping/apis/lastping-management-api/llms.txt) · [OpenAPI document](https://skmtc.dev/lastping/apis/lastping-management-api/revisions/bbc6eda91c46?raw)
