Skip to main content
Cypress CloudFree Trial

Webhooks Integration

Webhooks let Cypress Cloud send a real-time HTTP request to an endpoint you own whenever a selected event occurs, for example when a run finishes.

Why Cypress Cloud Webhooks​

A webhook lets you build custom automations: update a dashboard, open a ticket, gate a deployment, or forward results into any internal system.

Following the increased pace of AI-driven development, custom workflows have become more important than ever. You may use the initial "run completed" webhook in multiple ways:

  • Triage runs programmatically for certain characteristics that require attention, then dive deeper using tools like Cypress Cloud CLI or MCP to retrieve specific failures or other details.
  • Automatically feed results into your own custom real-time reporting.
  • Create Jira issues based on failed runs.
  • Deliver customized Slack notifications, or notifications in any platform, based on criteria that are not customizable in Cypress Cloud.

While Cypress Cloud already has many built-in methods of notifications related to run statuses (for example, Desktop notifications and Slack messages), webhooks deliver a raw JSON payload to your own service.

note

Early access: Webhooks will be available on all paid plans and during trials. The feature is rolling to admins and owners of Cypress Cloud organizations gradually, and may not yet be available for every organization. If you do not see webhooks in your project settings but want to try it early, contact your Cypress representative.

Getting started​

You need two things to get started:

  • The ability to manage webhooks for the project. You must be an Owner, Admin, or Team Admin of your Cypress Cloud organization organization. See User roles for details. Anyone who can view the project can view webhooks, but only those roles can create, edit, enable, disable, delete, test, or redeliver them.
  • A publicly-reachable endpoint that accepts POST requests. HTTPS is recommended. Cypress Cloud will not deliver to private, loopback, or internal network addresses (see Security). HTTP URLs are accepted, but be aware HTTP payloads travel unencrypted.

Webhooks are configured per project. You can create up to 5 webhooks per project.

Create a webhook​

  1. Open your project and click Settings at the bottom of the project sidebar.

    The Settings link at the bottom of the project sidebar
  2. Open the General tab and locate the Webhooks panel.

  3. Click Add webhook.

The Webhooks panel in Project Settings with an Add webhook button

In the Add webhook dialog, configure the following:

  • Payload URL (required): The URL Cypress Cloud will send a POST request to, for example https://example.com/receive-webhook.
  • Secret (optional, recommended): A signing secret used to verify that a request genuinely came from Cypress Cloud. Provide your own value (at least 16 characters) or click Generate secret to create a random one. Copy and store the secret somewhere safe. It is shown only once and cannot be viewed again. If you lose it, you can rotate it later by editing the webhook. See Verify webhook signatures.
  • Events that will trigger webhook: Select which events should be delivered to this endpoint. At least one event is required. Currently, only the run.completed event is available, so it is preselected by default and cannot be removed.
  • Enable this webhook: Whether the webhook is active. Enabled by default; disabled webhooks not called when
The Add webhook dialog with Payload URL, Secret, events, and enable toggle

Click Add webhook to save. Cypress Cloud will begin delivering the selected events to your endpoint.

Send a test event​

To confirm your endpoint is reachable and your verification logic works, use Send test on an enabled webhook. This delivers a synthetic webhook.ping event through the same signing and delivery pipeline as real events, so it exercises your signature verification exactly as a run.completed event would.

The webhook.ping request body is:

{
"message": "Webhook ping"
}
Sending a test event from a webhook row and viewing its delivery status

Test events are delivered once and are not retried, regardless of the outcome. The resulting delivery appears in the webhook's delivery history like any other.

Manage webhooks​

Each webhook appears as a row in the Webhooks panel showing its URL, an enable/disable toggle, and the status of its most recent delivery (for example, Last delivery succeeded, Retrying, Last delivery failed, Delivering…, or Never triggered). Use the inline controls and the Options (β‹―) menu to manage it:

  • Enable or disable: Toggle the switch on the row. A disabled webhook keeps its configuration but receives no deliveries.
  • Edit: Open the Options menu and select Edit to change the URL, events, or secret.
    • Generate new secret: Leaving the Secret field blank keeps the existing secret. To rotate it, enter a new value (at least 16 characters) or click Generate new secret. Copy the new secret and store it somewhere safe. It is shown only once. After you save, the previous secret no longer verifies requests, so update your endpoint to use the new value.
  • Recent deliveries: Open the Options menu and select Recent deliveries to review delivery history. See Delivery history and retries.
  • Delete: Open the Options menu and select Delete. This permanently removes the webhook and its delivery history. This cannot be undone.
A configured webhook row with its status, enable toggle, and actions menu
note

A project can have at most 5 webhooks. When you reach the limit, the Add webhook button is disabled until you delete an existing webhook.

Events​

Currently only one event in the Cypress Cloud lifecycle triggers a webhook:

EventDescription
run.completedSent when a Cypress Cloud run finished and resolves to one of the following states: passed, failed, errored, timedOut, or cancelled.

There is also a special webhook.ping event that is only sent by the Send a test event action. You cannot subscribe to it directly.

Payload​

Cypress Cloud sends a POST request whose body is the event payload as JSON. Metadata about the delivery travels in X-Cypress-* request headers rather than in the body.

Request headers​

Every delivery includes the following headers:

HeaderDescription
Content-TypeAlways application/json.
X-Cypress-EventThe event type, for example run.completed.
X-Cypress-Event-IdUnique ID for the event. Stable across retries. Use it to deduplicate.
X-Cypress-Event-VersionThe payload schema version, for example 1.
X-Cypress-Request-IdUnique ID for this individual delivery attempt. A new value on every retry.
X-Cypress-TimestampThe time the request was sent, as a Unix timestamp in seconds.
X-Cypress-Idempotency-KeyStable key for this event and webhook. Identical across retries of the same event.
X-Cypress-SignatureHMAC-SHA256 signature of the payload. Only present when a signing secret is set. See Verify webhook signatures.
tip

Because X-Cypress-Event-Id (and X-Cypress-Idempotency-Key) stay constant across retries, your endpoint may receive the same event more than once. Treat delivery as at-least-once and make your handler idempotent by keying on X-Cypress-Event-Id.

run.completed payload​

The run.completed payload (version 1) is a flat JSON object with the following fields:

FieldTypeDescription
runNumbernumberThe run's number in Cypress Cloud.
runUrlstringLink to the run in Cypress Cloud.
projectIdstringThe Project ID used for the run.
projectNamestringThe project name, which can be chosen in your Cypress Cloud settings.
statusstringRun status: passed, failed, errored, timedOut, or cancelled.
statusReasonstringHuman-readable explanation of the status.
createdAtstringISO 8601 timestamp when the run started.
completedAtstring | nullISO 8601 timestamp when the run finished.
totalDurationnumber | nullTotal run duration in milliseconds.
totalTestsnumber | nullTotal number of tests.
totalPassednumber | nullNumber of passed tests.
totalFailednumber | nullNumber of failed tests.
totalPendingnumber | nullNumber of pending tests.
totalSkippednumber | nullNumber of skipped tests.
flakyTestCountnumber | nullNumber of passing tests that failed at least one attempt and passed using retries.
totalSpecsnumber | nullNumber of spec files.
commitShastring | nullThe commit SHA the run was recorded against.
commitBranchstring | nullThe branch name.
commitMessagestring | nullThe commit message.
commitAuthorNamestring | nullThe commit author's name.
commitAuthorEmailstring | nullThe commit author's email.
commitUrlstring | nullLink to the commit.
ciProviderstring | nullThe detected CI provider, for example circleci.
ciBuildIdstring | nullThe CI build ID.
ciUrlstring | nullLink to the CI build.
ciPullRequestIdstring | nullThe pull request ID, when available.
ciPullRequestUrlstring | nullLink to the pull request, when available.
tagsstring[]The tags the run was recorded with (sorted).
groupsstring[]The run groups (sorted).
caution

Missing fields: If you are missing expected commit information or CI details, ensure the related environment variables are present in the context where Cypress executes. For example, when executing Cypress in a container, your CI identifiers and related build information may need to be set explicitly.

Example run.completed request body
{
"runNumber": 42,
"runUrl": "https://cloud.cypress.io/projects/my-project/runs/42",
"projectId": "my-project",
"projectName": "My Project",
"status": "passed",
"statusReason": "Run passed",
"createdAt": "2026-01-01T00:00:00.000Z",
"completedAt": "2026-01-01T00:05:00.000Z",
"totalDuration": 300000,
"totalTests": 100,
"totalPassed": 95,
"totalFailed": 0,
"totalPending": 2,
"totalSkipped": 3,
"flakyTestCount": 0,
"totalSpecs": 4,
"commitSha": "abc123",
"commitBranch": "main",
"commitMessage": "fix: something",
"commitAuthorName": "Ada Lovelace",
"commitAuthorEmail": "[email protected]",
"commitUrl": "https://github.com/cypress-io/cypress-services/commit/abc123",
"ciProvider": "circleci",
"ciBuildId": "circleci-workflow-777",
"ciUrl": "https://circleci.com/gh/cypress-io/cypress-services/555",
"ciPullRequestId": null,
"ciPullRequestUrl": null,
"tags": ["canary", "nightly"],
"groups": ["chrome", "firefox"]
}

Verify webhook signatures​

When you configure a signing secret, Cypress Cloud signs every request so your endpoint can confirm that a payload genuinely came from Cypress Cloud and was not altered in transit. We strongly recommend setting a signing secret for every webhook. Because your Payload URL is a publicly reachable endpoint, anyone who discovers it could send it forged requests; verifying the signature lets your handler reject anything that isn't authentically from Cypress Cloud.

The signing secret provides three things:

  • Authenticity: proof the request came from Cypress Cloud, not an impersonator.
  • Integrity: proof the payload was not tampered with, since any change invalidates the signature.
  • Replay protection: the signature covers a timestamp, so you can reject stale requests that are replayed later.
caution

A signing secret does not encrypt the payload. Use HTTPS when the payload must stay confidential. Over HTTP, anyone on the network path can still read the request body and headers. The HMAC still proves the request came from Cypress Cloud and was not modified, because the secret itself is never sent.

How the signature is computed​

When a secret is set, Cypress Cloud includes an X-Cypress-Signature header:

X-Cypress-Signature: sha256=<hex-encoded HMAC>

The signature is an HMAC-SHA256, keyed with your signing secret, of the string `${timestamp}.${rawBody}` (the value of the X-Cypress-Timestamp header, a literal ., then the exact raw bytes of the request body). The result is hex-encoded and prefixed with sha256=.

To verify a request:

  1. Read the X-Cypress-Signature and X-Cypress-Timestamp headers and the raw request body (before any JSON parsing; parsing and re-serializing can change the bytes and break verification).
  2. Optionally reject the request if the timestamp is too old (for example, more than five minutes) to guard against replay attacks.
  3. Recompute HMAC-SHA256(secret, "${timestamp}.${rawBody}"), hex-encode it, and prefix sha256=.
  4. Compare it to X-Cypress-Signature using a constant-time comparison.
note

If a webhook has no signing secret, Cypress Cloud still delivers events, but the X-Cypress-Signature header is omitted. Configure a secret to enable verification.

Verify a signature in your handler​

The following example handlers add a route to an existing Express or Flask app. They read the raw body, reject stale timestamps, recompute the HMAC, and compare it in constant time.

Node.js and Python examples
const crypto = require('crypto')

const SIGNING_SECRET = process.env.CYPRESS_WEBHOOK_SECRET

// Register this route before express.json() or any other body parser.
// Those parsers consume the body and would break signature verification.
app.post(
'/receive-webhook',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.get('X-Cypress-Signature')
const timestamp = req.get('X-Cypress-Timestamp')
const rawBody = req.body // Buffer

if (!signature || !timestamp) {
return res.status(400).send('Missing signature headers')
}

// Reject stale requests to protect against replay attacks (5 minute window).
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
return res.status(400).send('Timestamp outside of tolerance')
}

const expected =
'sha256=' +
crypto
.createHmac('sha256', SIGNING_SECRET)
.update(`${timestamp}.${rawBody.toString('utf8')}`)
.digest('hex')

const valid =
signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))

if (!valid) {
return res.status(401).send('Invalid signature')
}

const event = JSON.parse(rawBody.toString('utf8'))
// ...handle the event...

res.status(200).send('ok')
}
)

Delivery history and retries​

Open the Options menu on a webhook and select Recent deliveries to review its delivery history. Cypress Cloud retains up to 100 deliveries per webhook, newest first. Each entry shows the event type and version, timestamps, and a status. Expand a delivery to inspect the full per-attempt request and response: headers, bodies, response status, and any error details.

The Recent deliveries dialog listing deliveries with their statuses

Delivery statuses:

  • Delivered: The endpoint returned a success response.
  • Pending: The delivery is queued or in progress.
  • Retrying: An attempt failed and another is scheduled.
  • Failed. No more retries.: The delivery failed and will not be retried. Automatic retries are exhausted, the failure is not retryable (for example a 3xx or a 4xx other than 408 or 429), or the event was a test (webhook.ping).
An expanded delivery showing per-attempt request and response details
note

For your protection, sensitive request headers (such as Authorization and Cookie) are redacted in the stored delivery history.

Retry behavior​

If a delivery fails, Cypress Cloud retries it until it succeeds or reaches 10 total attempts (the first attempt plus up to 9 retries), using an exponential backoff schedule with jitter. Cypress Cloud retries on network/transport errors and on HTTP 408, 429, and 5xx responses. Other completed responses (3xx and other 4xx) and blocked destinations are treated as permanent failures and are not retried. Test (webhook.ping) events are never retried.

Each attempt is listed under that delivery in Recent deliveries. Expand the delivery to see Attempt 1, Attempt 2, and so on. Automatic retries are tagged Retry; a redelivery you trigger is tagged Manual retry.

To resend a failed or exhausted delivery yourself, expand it in the Recent deliveries dialog and choose Redeliver. A redelivery reuses the original payload and keeps the same X-Cypress-Event-Id, so your idempotency logic still recognizes it as the same event.

Security​

We recommend that you always use an https:// Payload URL so payloads are encrypted in transit.

In addition, Cypress Cloud applies several protections to webhook delivery:

  • Endpoint restrictions: Destination URLs are resolved and validated before each delivery. Requests to private, loopback, and link-local addresses are blocked to prevent server-side request forgery (SSRF).
  • No redirects: Cypress Cloud does not follow HTTP redirects; the Payload URL must respond directly.
  • Timeouts and limits: Each attempt times out after 10 seconds, and only the first 8Β KB of your response body is read and stored.
  • Secret storage: Signing secrets are encrypted at rest and are never returned by the Cypress Cloud API. A secret is shown only once, when you set it.

Troubleshooting​

Run completed webhook fires too late​

The run.completed webhook is delivered when Cypress Cloud marks the run as complete. If you record in parallel or with multiple groups, Cypress Cloud waits for the Run Completion Delay (60 seconds by default) after the last known group finishes, in case more groups still join. That wait is what delays the webhook, because if all tests haven't been recorded yet, the final state of the run is not known.

To complete the run as soon as your expected groups have finished, call the Run Completion API. Cypress Cloud then skips the remaining delay, and the webhook is delivered when in-progress groups finish.