Webhooks
Webhooks allow you to receive real-time HTTP notifications when events occur in QA Sphere. When an event triggers, QA Sphere sends an HTTP POST request to your configured endpoint with details about the event.
Creating a Webhook
Navigate to Settings > Integrations > Webhooks and click Create Webhook.
Connection Settings
| Field | Description |
|---|---|
| Name | A descriptive name for your webhook |
| Endpoint | The HTTPS URL where webhook payloads will be sent |
| Secret | Optional shared secret for signature verification (recommended) |
| Event Types | Select which events should trigger this webhook |
| Enabled | Toggle to enable or disable the webhook |
Request Customization
You can customize the HTTP request sent to your endpoint:
- Headers: Add custom headers (one per line, format:
Header-Name: value) - Payload: Define a custom JSON payload template with variable substitution
Project Permissions
Control which projects trigger the webhook:
- All Projects: Webhook fires for events in any project
- Specific Projects: Select individual projects to monitor
Event Types
| Event | Description |
|---|---|
plan_created | A test plan was created |
plan_updated | A test plan was updated, closed, reopened, or deleted |
run_created | A test run was created |
run_updated | A test run was updated, closed, reopened, or deleted |
tcase_created | A single test case was created |
tcases_created | Multiple test cases were created in a batch operation |
tcase_edited | A single test case was edited |
tcases_edited | Multiple test cases were edited in a batch operation |
result_created | A single test result was recorded |
results_created | Multiple test results were recorded in a batch operation |
Template Variables
Use template variables in your custom payload and headers using the ${variable} syntax.
Universal Variables
Available for all event types:
| Variable | Type | Description |
|---|---|---|
${event_type} | string | The event type (e.g., plan_created, run_updated) |
${timestamp} | string | RFC3339 timestamp when the event occurred |
${project_id} | string | The project's unique identifier |
${project_code} | string | The project's short code (e.g., PRJ) |
${id} | string | The entity's unique identifier |
${name} | string | The entity's title or name |
${url} | string | Direct URL to view the entity in QA Sphere |
${event_creator} | string | Name of the user who performed the action |
${event_created} | string | RFC3339 timestamp when the action occurred |
${entity_creator} | string | Name of the user who originally created the entity |
${entity_created} | string | RFC3339 timestamp when the entity was created |
${is_deleted} | boolean | Whether the entity is deleted (true or false) |
Plan-Specific Variables
| Variable | Type | Description |
|---|---|---|
${event_description} | string | Description of the action (e.g., "Plan created") |
${configuration} | string | Comma-separated list of configuration names |
${completed_on} | string | RFC3339 timestamp when the plan was closed (empty if open) |
Run-Specific Variables
| Variable | Type | Description |
|---|---|---|
${event_description} | string | Description of the action (e.g., "Run created") |
${assigned_to} | string | Name of the assigned user (empty if unassigned) |
${configuration} | string | Configuration name (empty if none) |
${completed_on} | string | RFC3339 timestamp when the run was closed (empty if open) |
Test Case-Specific Variables
| Variable | Type | Description |
|---|---|---|
${tcase_seq} | string | Test case sequence number |
${tcase_priority} | string | Priority level (low, medium, high) |
Result-Specific Variables
| Variable | Type | Description |
|---|---|---|
${event_description} | string | Description including test case and run titles |
${result_status} | string | Result status (e.g., passed, failed, skipped, blocked) |
${result_comment} | string | Comment added to the result (empty string if no comment was added) |
${result_links} | string | Comma-separated list of link URLs (empty if no links) |
Bulk Event Variables
For results_created events:
| Variable | Type | Description |
|---|---|---|
${ids} | array | Array of result IDs (when used as exact value) |
${items} | array | Array of item objects with individual details |
Each item in ${items} contains:
id- Result IDurl- URL to view the resultresult_status- Result status (e.g.,passed,failed)result_comment- Comment added to the resultresult_links- Comma-separated list of link URLs
Payload Examples
Default Payload
When no custom payload is specified, QA Sphere sends:
{
"event_type": "run_created",
"timestamp": "2026-01-21T11:07:57Z",
"data": {
"id": "22",
"name": "UI Testing Test Run",
"url": "https://example.qasphere.com/project/DB/run/22",
"project_id": "1CJjW1Amf_a8hximyr325zz",
"project_code": "DB",
"event_description": "Run created",
"event_creator": "Nick Lapis-Trout",
"event_created": "2026-01-21T11:07:57Z",
"entity_creator": "Nick Lapis-Trout",
"entity_created": "2026-01-21T11:07:57Z",
"is_deleted": false,
"assigned_to": "Nick Lapis-Trout",
"configuration": "Android",
"completed_on": ""
}
}
Custom Payload with Variables
{
"event": "${event_type}",
"project": "${project_code}",
"entity": {
"id": "${id}",
"name": "${name}",
"url": "${url}"
},
"actor": "${event_creator}",
"deleted": "${is_deleted}"
}
Typed Variable Substitution
When a variable is the exact and only value of a JSON field, it preserves its type:
{
"deleted": "${is_deleted}",
"result_ids": "${ids}"
}
Results in:
{
"deleted": true,
"result_ids": ["123", "456", "789"]
}
When a variable is embedded in a string, it becomes a string:
{
"message": "Entity ${id} was ${event_type}"
}
Results in:
{
"message": "Entity 42 was run_created"
}
Integration Examples
Slack
To send webhook notifications to Slack, create a Slack Incoming Webhook and use it as your endpoint. Configure a custom payload using Slack's Block Kit format:
Event Types: Select tcase_created to notify when new test cases are added.
Custom Payload:
{
"text": "${event_creator} created a new test case in ${project_code}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*${event_creator}* created a new test case:"
}
},
{
"type": "divider"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*<${url}|${name}>*\nProject: ${project_code}\nPriority: ${tcase_priority}"
}
}
]
}
This produces a Slack message like:
John Doe created a new test case:
Verify login with valid credentials Project: PRJ Priority: high
The text field serves as a fallback for notifications and accessibility. Always include it alongside blocks.
Request Headers
QA Sphere automatically includes these headers with every webhook request:
| Header | Description |
|---|---|
Content-Type | Always application/json |
X-Webhook-ID | Unique identifier for this delivery (for idempotency) |
X-Webhook-Timestamp | Unix timestamp when the request was sent |
X-Webhook-Signature | HMAC-SHA256 signature (only if secret is configured) |
Custom Headers
Add custom headers in the webhook configuration:
Authorization: Bearer your-token
X-Custom-Header: custom-value
X-Project: ${project_code}
The following headers cannot be overridden: X-Webhook-Timestamp, X-Webhook-Signature, X-Webhook-ID, Host, and proxy-related headers.
Signature Verification
When a secret is configured, QA Sphere signs the payload using HMAC-SHA256. We strongly recommend verifying signatures to ensure requests originate from QA Sphere.
Signature Format
The signature is provided in the X-Webhook-Signature header:
sha256=<hex-encoded-signature>
Verification Steps
- Get the timestamp from
X-Webhook-Timestamp - Get the raw request body
- Concatenate:
{timestamp}.{body} - Compute HMAC-SHA256 using your secret
- Compare with the signature (use constant-time comparison)
Example (Node.js)
const crypto = require('crypto')
function verifySignature(payload, timestamp, signature, secret) {
const message = `${timestamp}.${payload}`
const expectedSignature =
'sha256=' + crypto.createHmac('sha256', secret).update(message).digest('hex')
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))
}
Example (Python)
import hmac
import hashlib
def verify_signature(payload, timestamp, signature, secret):
message = f"{timestamp}.{payload}"
expected = "sha256=" + hmac.new(
secret.encode(),
message.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
Example (Go)
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
)
func verifySignature(payload string, timestamp int64, signature, secret string) bool {
message := fmt.Sprintf("%d.%s", timestamp, payload)
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(message))
expected := "sha256=" + hex.EncodeToString(h.Sum(nil))
return hmac.Equal([]byte(signature), []byte(expected))
}
Delivery Behavior
Retry Policy
QA Sphere retries failed deliveries with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 30 seconds |
| 3 | 2 minutes |
After three failed attempts, the delivery is abandoned and will not be retried.
Retry Conditions
Retries occur for:
- Network errors
- 5XX server errors
- 429 (Rate Limit) responses
Retries do not occur for:
- 4XX client errors (except 429)
Timeouts
- Connection timeout: 10 seconds
- Total request timeout: 30 seconds
Bulk Events
When many items are created in a single operation (more than 5 items), QA Sphere sends a summary event instead of individual item details:
- The
${name}variable contains a summary (e.g., "10 results created") - The
${items}array is omitted - The
${ids}array (for results) contains all IDs
Security Considerations
HTTPS Required
Webhook endpoints must use HTTPS. HTTP endpoints are not allowed.
Testing Webhooks
Use the Test Webhook button in the webhook configuration to send a test request. Test requests include:
{
"event_type": "test",
"timestamp": "2026-01-21T11:07:57Z",
"test": true,
"data": {
"text": "This is a test message"
}
}
- Always configure and verify webhook signatures
- Respond to webhooks quickly (within 10 seconds)
- Process webhook payloads asynchronously if needed
- Use the
X-Webhook-IDheader for idempotency - Return 2XX status codes to acknowledge receipt
Contact support for assistance with webhook configuration.