> ## Documentation Index
> Fetch the complete documentation index at: https://docs.testdino.com/llms.txt
> Use this file to discover all available pages before exploring further.

# TestDino Webhooks

> Subscribe to Playwright test run events over HTTP. Covers the event types, signature verification, retry behavior, and the 6 management operations.

TestDino webhooks POST a JSON payload to a URL you own when a Playwright test run starts or finishes. Each request is signed, so you can verify it came from TestDino before acting on it.

## Quick Reference

| Topic                                         | Summary                                   |
| :-------------------------------------------- | :---------------------------------------- |
| [Events](#events)                             | `RUN_STARTED` and `RUN_FINISHED`          |
| [Manage subscriptions](#manage-subscriptions) | 6 operations in the Public API            |
| [Verify the signature](#verify-the-signature) | HMAC-SHA256 over `<timestamp>.<raw body>` |
| [Delivery and retries](#delivery-and-retries) | 5 attempts, then the delivery is dropped  |

## Events

Subscribe to either event, or both.

| Event          | Fires when           |
| :------------- | :------------------- |
| `RUN_STARTED`  | A test run begins    |
| `RUN_FINISHED` | A test run completes |

`RUN_FINISHED` accepts an outcome filter so you only receive the runs you care about. It is ignored for `RUN_STARTED`.

| `conditions.outcome` | Delivers                     |
| :------------------- | :--------------------------- |
| `any`                | Every finished run           |
| `failed`             | Runs with 1 or more failures |
| `passed`             | Runs with no failures        |

## Manage subscriptions

Subscriptions are project-scoped and managed through the [Public API](/api-reference/overview). Every request uses a `td_pat_` personal access token as a Bearer token.

| Operation              | Endpoint                                           |
| :--------------------- | :------------------------------------------------- |
| List subscriptions     | `GET /{projectId}/webhooks`                        |
| Create a subscription  | `POST /{projectId}/webhooks`                       |
| Get a subscription     | `GET /{projectId}/webhooks/{webhookId}`            |
| Update a subscription  | `PATCH /{projectId}/webhooks/{webhookId}`          |
| Delete a subscription  | `DELETE /{projectId}/webhooks/{webhookId}`         |
| List delivery attempts | `GET /{projectId}/webhooks/{webhookId}/deliveries` |

Reads work for any member of the organization. Creating, updating, and deleting require an owner or admin role; other roles get `403 FORBIDDEN`.

```bash theme={null}
curl -X POST https://api.testdino.com/api/v1/public/{projectId}/webhooks \
  -H "Authorization: Bearer $TESTDINO_PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/hooks/testdino",
    "events": ["RUN_FINISHED"],
    "conditions": { "outcome": "failed" },
    "description": "Alert on failed runs"
  }'
```

<Warning>
  **Store the signing secret immediately**

  The `secret` is returned in cleartext exactly once, in the create response. Later reads never include it. If you lose it, delete the subscription and create a new one.
</Warning>

Full request and response schemas are in the [OpenAPI specification](https://docs.testdino.com/openapi.json) under the `Webhooks` tag.

## Verify the signature

Every request carries 3 headers.

| Header                 | Contains                                   |
| :--------------------- | :----------------------------------------- |
| `X-TestDino-Signature` | `t=<unix_seconds>,v1=<hex hmac-sha256>`    |
| `X-TestDino-Event`     | `RUN_STARTED` or `RUN_FINISHED`            |
| `X-TestDino-Delivery`  | Delivery identifier, stable across retries |

The signature is an HMAC-SHA256 of `<timestamp>.<raw request body>`, keyed with your signing secret. Compute it over the **raw bytes** you received: parsing and re-serializing the JSON changes the body and breaks the comparison.

```javascript theme={null}
import crypto from "node:crypto";

function verify(secret, rawBody, header, toleranceSeconds = 300) {
  const parts = Object.fromEntries(
    header.split(",").map((kv) => {
      const i = kv.indexOf("=");
      return [kv.slice(0, i).trim(), kv.slice(i + 1).trim()];
    })
  );

  const timestamp = Number(parts.t);
  if (!Number.isFinite(timestamp) || !parts.v1) return false;

  // Reject replayed requests outside the tolerance window.
  if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > toleranceSeconds) {
    return false;
  }

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");

  const a = Buffer.from(parts.v1, "hex");
  const b = Buffer.from(expected, "hex");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
```

Compare with a timing-safe function, and reject any request whose timestamp is more than 300 seconds from your clock.

## Delivery and retries

Respond with any `2xx` status to acknowledge a delivery. TestDino does not read the response body, and redirects are not followed.

A delivery that fails is retried 4 times, for 5 attempts total.

| Attempt | Retried after |
| :------ | :------------ |
| 1       | 1 minute      |
| 2       | 5 minutes     |
| 3       | 30 minutes    |
| 4       | 120 minutes   |
| 5       | Not retried   |

After 5 consecutive failed deliveries, the subscription is disabled and stops receiving events. Re-enable it by setting `active` to `true`, which also clears the failure count.

Inspect what happened with the deliveries endpoint, which returns each attempt with its status, HTTP status code, and response time.

```bash theme={null}
curl "https://api.testdino.com/api/v1/public/{projectId}/webhooks/{webhookId}/deliveries?status=failed" \
  -H "Authorization: Bearer $TESTDINO_PAT"
```

## Related

<CardGroup cols={3}>
  <Card title="API Reference" icon="code" href="/api-reference/overview">
    Every endpoint, grouped by resource.
  </Card>

  <Card title="Generate API Keys" icon="key" href="/guides/generate-api-keys">
    Create and scope a `td_pat_` token.
  </Card>

  <Card title="Developer Portal" icon="code-branch" href="/developers">
    The OpenAPI spec, MCP server, and quickstarts.
  </Card>
</CardGroup>
