On this page
Use this reference when building a receiver for TestCollab project Webhooks. TestCollab sends JSON POST requests to the HTTPS endpoints configured by an Administrator. For the setup steps, see Set Up Project Webhooks.
Supported webhook events
There are 15 business event types. A test plan groups test cases for execution, a test plan run is one iteration of that plan, and an executed test case is a case's execution record within a run and configuration.
| Record | Event types |
|---|---|
| Test case |
test_case.created, test_case.updated,
test_case.deleted
|
| Suite | suite.created, suite.updated, suite.deleted |
| Test plan |
test_plan.created, test_plan.updated,
test_plan.deleted, test_plan.archived,
test_plan.unarchived, test_plan.status_changed
|
| Executed test case |
executed_test_case.status_changed, executed_test_case.assigned
|
| Test plan run | test_plan_run.created |
webhook.test is a separate synthetic event sent by Send test. It
does not require a catalog subscription and makes one delivery attempt.
executed_test_case.assigned is sent when an execution gets an assignee or a different
assignee. A new execution that already has an assignee, for example in a new run or after test
cases are added to a plan, also sends it. Removing an assignee sends no event. The event names
assigned_to in changed_fields, and previous_data holds the
earlier assignee, or null for a first assignment. Plan snapshots include
assigned_to, and execution snapshots include the execution's assignee.
Open Settings > Webhooks > Events > Catalog and select View example to inspect an illustrative payload. For actual saved values, select View payload under Occurred events.
The signed request envelope
The HTTP body is a JSON object with these fields:
| Field | Meaning |
|---|---|
payload |
The event serialized as a JSON string. |
timestamp |
The signing time for this attempt, in Unix seconds. |
secret_version |
The version of the project's signing secret used for this attempt. |
signature |
A hexadecimal HMAC-SHA256 signature. |
Parse the outer JSON to read these fields. Verify the signature using the unchanged
payload string before parsing the inner event. Do not reformat or reserialize that
string first: even a whitespace change produces a different signature.
Verify the signature
Calculate HMAC-SHA256 over String(timestamp) + "." + payload, using the shared secret
as a UTF-8 string. Although a generated secret looks hexadecimal, do not hex-decode it. Compare
the calculated digest with signature using a constant-time comparison.
The following Node.js helper checks the signature, a five-minute timestamp window, the expected secret version, and the project ID. Store the secret in your receiver's secret store or an environment variable. Set the version to the value shown in TestCollab.
const crypto = require("crypto");
function verifyWebhook(envelope, secret, expectedVersion, expectedProject, now = Date.now()) {
if (!secret || !envelope || typeof envelope.payload !== "string" ||
!Number.isSafeInteger(envelope.timestamp) ||
envelope.secret_version !== expectedVersion ||
typeof envelope.signature !== "string" ||
!/^[a-f0-9]{64}$/i.test(envelope.signature)) {
throw new Error("Invalid webhook envelope");
}
const age = Math.abs(Math.floor(now / 1000) - envelope.timestamp);
if (age > 300) throw new Error("Webhook timestamp outside the acceptance window");
const expected = crypto.createHmac("sha256", secret)
.update(String(envelope.timestamp) + "." + envelope.payload, "utf8")
.digest();
const received = Buffer.from(envelope.signature, "hex");
if (!crypto.timingSafeEqual(expected, received)) {
throw new Error("Invalid webhook signature");
}
const event = JSON.parse(envelope.payload);
if (!event || event.schema_version !== "1" ||
event.project !== expectedProject ||
typeof event.event_id !== "string" || !event.event_id) {
throw new Error("Unexpected webhook event");
}
return event;
}
Call this helper with your parsed request body and trusted receiver configuration. Reject verification failures before making changes in the receiving system. This helper verifies the request; your handler must also deduplicate events, validate the event-specific fields it uses, and process only the event types it supports.
The five-minute window is a receiver policy in this example. Keep your receiver's clock
synchronized and choose a window appropriate for your integration. Use the envelope's
timestamp, rather than the event's occurred_at, for this check. A retry
signs the original event again with a new timestamp and the current secret.
Inside a business event payload
This shortened example shows the inner event after verification and parsing:
{
"schema_version": "1",
"event_id": "00000000-0000-4000-8000-000000000001",
"event_type": "suite.updated",
"occurred_at": "2026-10-28T09:00:00.000Z",
"project": 123,
"resource_type": "suite",
"resource_id": 456,
"resource_version": 2,
"correlation_id": "00000000-0000-4000-8000-000000000002",
"changed_fields": ["title"],
"cause": {
"action": "update",
"root_resource_type": "suite",
"root_resource_id": 456
},
"data": {
"suite": { "id": 456, "title": "Checkout regression" }
}
}
event_ididentifies the event across automatic retries and manual replays.resource_typeandresource_ididentify the changed record.-
resource_versionincreases for events belonging to that record in the project. -
correlation_idgroups related events from an operation. See Handle bulk changes. changed_fieldsnames the fields associated with the change.-
causedescribes the action and its originating record, including cascaded changes. -
datacontains the saved snapshot under the resource type's key. For deleted records it contains the preceding snapshot. Some events also includeprevious_data, for example a status transition or an assignment.
Test case and test plan snapshots include custom fields. Test plan status snapshots include a numeric status, its label, and progress information. Execution status snapshots use a status string and identify the test case, plan, run, configuration, and assignee. Use the catalog and actual event payloads when mapping these fields to your system; examples are not full API resource responses.
Handle duplicate and out-of-order events
Delivery is at least once, so your receiver must accept the possibility of repeat requests.
Persist processed event_id values and make side effects idempotent. If a request
timed out after you processed it, acknowledge the duplicate without repeating the operation.
Coordinate deduplication with your own write or durable queue so a crash cannot silently lose the
event.
Do not assume arrival order. When maintaining a current-state copy, compare
resource_version for the same project, resource type, and resource ID before
replacing a newer snapshot. If your workflow needs every transition, process each distinct event
ID even when a later snapshot has already arrived.
Return an HTTP 2xx response after you have accepted the event durably. TestCollab treats any 2xx response as delivered, regardless of an error described in its body. Return an appropriate non-2xx response for a failure and inspect the retry behavior when designing your handler. General HTTP redirects are not followed, so configure the final receiver URL.
Handle bulk changes
One action in TestCollab can change many records at once. TestCollab then sends one event for each
changed record, not one event for the action. All events of one action carry the same
correlation_id.
These actions can send many events:
- A bulk edit of test cases or test plans
- An import of test cases, or a BDD feature file sync
- A copy of test cases, suites or test plans
- Move to Trash or Restore, for example a suite with all its test cases
- A bulk status change of results in a test plan run
- A rename of a custom field or its label: every test case or test plan that uses the field changes
- The removal of a project member, when the member's default assignments move to another person
To handle a bulk change in your receiver:
-
Group the events by
correlation_id.cause.actiontells you what the person did, for exampleimport,copy,trashorstatus_change.cause.root_resource_typeandcause.root_resource_idname the record that the person acted on. A record that changed because of another record has the actioncascade. -
Do not expect the whole group at once. TestCollab delivers a large group in
parts, so the last events can arrive several minutes after the first. Treat a group as complete
when no new event with its
correlation_idarrives for a few minutes. Then act once, for example with one chat message: "120 test cases imported". - Accept each event quickly. Store it and return a 2xx response, then do the slow work later. A slow response makes the delivery time out, and TestCollab retries a delivery that timed out.
-
Keep the rules for single events. Remove duplicates by
event_idand order byresource_version, as in Handle duplicate and out-of-order events. - Subscribe only to the events you need. An endpoint receives only the event types that you select for it, so a smaller selection means fewer events in a bulk change.


