Webhooks let ChangeTower notify your own systems whenever something happens in a workspace. When a notification is issued (for example, a monitored page changes and triggers an alert), ChangeTower sends an HTTP POST request to every webhook endpoint you’ve configured in that workspace.
This guide covers everything you need to integrate with ChangeTower webhooks, including creating endpoints, verifying signatures, managing signing secrets, and understanding retry behavior.
Before you begin
To use webhooks, you’ll need:
An HTTPS endpoint that can accept
POSTrequestsAccess to your workspace settings
A secure place to store your webhook signing secret
ChangeTower currently sends one webhook event type: monitor.notification.created.
Quick Start
Create a webhook endpoint in your workspace settings.
Copy and securely store the signing secret.
Configure your application to accept
POSTrequests.Verify the webhook signature on every request.
Return a
2xxresponse within 15 seconds.Process events asynchronously.
The rest of this guide covers the details of securing and managing your integration.
Creating an endpoint
Webhook endpoints are scoped to a workspace and managed from that workspace’s settings.
Open the workspace you want to receive events from.
Go to Settings → Webhooks.
Click Create endpoint.
Enter:
URL - the HTTPS URL ChangeTower should
POSTto. Plain HTTP is not accepted. The URL must begin withhttps://and be no longer than 2048 characters.Description - optional, for your own reference.
Save the endpoint.
When the endpoint is created, ChangeTower generates a unique signing secret. You’ll use this secret to verify that incoming requests genuinely originated from ChangeTower.
New endpoints are enabled by default and begin receiving events immediately.
What triggers a webhook
Whenever a notification is created in a workspace, ChangeTower sends a monitor.notification.created webhook event to every enabled endpoint.
This is currently the only webhook event type.
The request body is a JSON object describing the notification:
{
"id": 123,
"type": "keyword-change",
"created_at": "2026-03-22T12:34:56.000000Z"
}
Payload fields
Field | Description |
id | Unique notification identifier |
type | Notification type |
created_at | Timestamp when the notification was created (UTC) |
Additional fields may be added in future API versions. Your integration should ignore any fields it does not recognize.
Example webhook request
POST /webhooks/changetower HTTP/1.1
Host: example.com
Content-Type: application/json
Webhook-Id: 6af2c7f4-1a3d-4a64-a5a1-5f5a71e6dcb0
Webhook-Timestamp: 1707696000
Webhook-Signature: t=1707696000,v1=5257a869e7e...
Webhook-Api-Version: 2026-02-12
{
"id": 123,
"type": "keyword-change",
"created_at": "2026-03-22T12:34:56.000000Z"
}
Each delivery is sent as an HTTP POST request with the following headers:
Header | Description |
Webhook-Id | Unique UUID for the webhook delivery. Use this for idempotency. |
Webhook-Timestamp | Unix timestamp (seconds) of when the request was signed. |
Webhook-Signature | HMAC signature(s) used to verify authenticity. |
Webhook-Api-Version | Payload schema version used for the request. |
Content-Type | Always application/json. |
API versioning
The Webhook-Api-Version header identifies the payload schema version used to generate the request. Future versions may introduce additional fields while maintaining backwards compatibility.
Idempotency
Webhook deliveries may be retried. Your endpoint should treat Webhook-Id as an idempotency key and ensure the same webhook can be processed safely more than once.
The request times out after 15 seconds.
Responding to deliveries
To acknowledge a webhook successfully, return any 2xx HTTP status code within 15 seconds.
Response | Result |
2xx | Delivery marked successful |
3xx | Treated as failure |
4xx | Treated as failure |
5xx | Treated as failure |
Timeout (>15 seconds) | Treated as failure |
For best results, acknowledge the webhook immediately and perform any longer-running work asynchronously.
Verifying signatures
Every webhook is signed using HMAC-SHA256 with your endpoint’s signing secret. Verifying the signature confirms that a request originated from ChangeTower and was not modified in transit.
Always verify webhook signatures before trusting the payload.
Signature format
The Webhook-Signature header looks like this:
t=1707696000,v1=5257a869e7e...,v1=8f3c2b1d0a...
t=<timestamp>— the same value as theWebhook-Timestampheader.v1=<hex>— one or more HMAC-SHA256 signatures.
Multiple v1 values are only present during a signing secret rotation grace period.
How to verify a webhook
Read the raw request body as bytes. Do not re-serialize the JSON.
Read the
Webhook-Timestampheader.Construct the signed message as:
<timestamp>.<raw_body>
Compute an HMAC-SHA256 hash using your signing secret.
Parse the
Webhook-Signatureheader and extract allv1values.Compare your computed signature against each provided signature using a constant-time comparison.
If any signature matches, the request is authentic.
You should also reject requests whose timestamp differs significantly from your server’s current time. A five-minute tolerance window is generally recommended.
Example: Node.js
const crypto = require('crypto');
function verifyChangeTowerWebhook(rawBody, headers, secret) {
const timestamp = headers['webhook-timestamp'];
const signatureHeader = headers['webhook-signature'];
if (!timestamp || !signatureHeader) return false;
const ageSeconds = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp));
if (ageSeconds > 300) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
const provided = signatureHeader
.split(',')
.filter((part) => part.startsWith('v1='))
.map((part) => part.slice(3));
return provided.some((sig) =>
sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)),
);
}
Example: PHP
function verifyChangeTowerWebhook(string $rawBody, array $headers, string $secret): bool
{
$timestamp = $headers['Webhook-Timestamp'] ?? null;
$signatureHeader = $headers['Webhook-Signature'] ?? null;
if (! $timestamp || ! $signatureHeader) {
return false;
}
if (abs(time() - (int) $timestamp) > 300) {
return false;
}
$expected = hash_hmac('sha256', $timestamp.'.'.$rawBody, $secret);
foreach (explode(',', $signatureHeader) as $part) {
if (str_starts_with($part, 'v1=') && hash_equals(substr($part, 3), $expected)) {
return true;
}
}
return false;
}
Managing the signing secret
Revealing the secret
The signing secret is shown when the endpoint is created and can be revealed again at any time from the endpoint details page.
To view the current secret:
Open the webhook endpoint.
Click Reveal secret.
Treat the signing secret like a password. Store it in your application’s secret manager or environment configuration and never commit it to source control.
Rotating the secret
Rotate your signing secret if you believe it has been exposed or as part of your organization’s regular security practices.
To rotate a secret:
Open the webhook endpoint.
Click Roll secret.
Choose a grace period.
Confirm the rotation.
How secret rotation works
A new signing secret is generated immediately.
New webhook deliveries begin using the new secret.
The previous secret remains valid during the selected grace period.
During the grace period, webhook deliveries are signed with both secrets.
Once the grace period expires, the old secret is permanently removed.
Because deliveries are signed with both secrets during the grace period, your integration can be updated without downtime.
Recommended process:
Rotate the secret with a grace period long enough to deploy configuration changes.
Update your application to use the new secret.
Verify webhook deliveries are succeeding.
Allow the grace period to expire.
Disabling and re-enabling an endpoint
You can disable a webhook endpoint at any time.
While disabled:
No new webhook deliveries are sent.
No retries are scheduled.
Endpoint configuration and delivery history are preserved.
To resume deliveries, re-enable the endpoint from the endpoint details page.
Events generated while an endpoint is disabled are not replayed.
Automatic disabling
If an endpoint accumulates 50 consecutive failed deliveries (after all retries have been exhausted), ChangeTower automatically disables it.
This prevents unnecessary retries and resource consumption. After resolving the issue, you can manually re-enable the endpoint.
Retries
ChangeTower expects a successful 2xx response within 15 seconds.
The following are treated as delivery failures:
Non-
2xxHTTP responsesRequest timeouts
Connection failures
TLS or certificate errors
Failed deliveries are automatically retried.
Retry schedule
Each webhook delivery receives up to 6 total attempts (the initial delivery plus 5 retries).
Attempt | Delay before attempt |
1 | Immediate |
2 | ~1 minute |
3 | ~5 minutes |
4 | ~30 minutes |
5 | ~2 hours |
6 | ~6 hours |
Retry delays include approximately ±20% random jitter to help distribute retry traffic.
If all attempts fail, the delivery is marked as failed and no further automatic retries occur.
You can manually retry failed deliveries from the endpoint’s delivery history.
Delivery history
Every webhook attempt, whether automatic or manual, is recorded in the endpoint’s delivery history.
For each attempt, you’ll see:
Attempt number
Whether the attempt was automatic or manual
HTTP status code returned by your server
Truncated response body (up to 1024 characters)
Response headers
Total request duration
Error details if no response was received
Use the delivery history to inspect failed requests, response codes, timeouts, and webhook payloads when troubleshooting integration issues.
Troubleshooting
Signature verification fails
Common causes include:
Using parsed JSON instead of the raw request body
Using the wrong signing secret
Omitting the timestamp when generating the signature
Re-serializing JSON before verification
Character encoding differences between signing and verification
Deliveries are timing out
If deliveries are timing out:
Return a response before performing long-running work
Process webhook events asynchronously
Avoid waiting on external APIs before responding
Avoid lengthy database operations before returning a response
Endpoint was automatically disabled
Endpoints are automatically disabled after 50 consecutive failed deliveries.
Review the endpoint’s delivery history, resolve the underlying issue, and re-enable the endpoint.
Receiving duplicate events
Duplicate deliveries can occur when a webhook is retried.
Use the Webhook-Id header as an idempotency key and ensure your application can safely ignore duplicate deliveries.
