Skip to main content

Webhooks Integration

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 POST requests

  • Access 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

  1. Create a webhook endpoint in your workspace settings.

  2. Copy and securely store the signing secret.

  3. Configure your application to accept POST requests.

  4. Verify the webhook signature on every request.

  5. Return a 2xx response within 15 seconds.

  6. 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.

  1. Open the workspace you want to receive events from.

  2. Go to Settings → Webhooks.

  3. Click Create endpoint.

  4. Enter:

    • URL - the HTTPS URL ChangeTower should POST to. Plain HTTP is not accepted. The URL must begin with https:// and be no longer than 2048 characters.

    • Description - optional, for your own reference.

  5. 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 the Webhook-Timestamp header.

  • 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

  1. Read the raw request body as bytes. Do not re-serialize the JSON.

  2. Read the Webhook-Timestamp header.

  3. Construct the signed message as:

    <timestamp>.<raw_body>
  4. Compute an HMAC-SHA256 hash using your signing secret.

  5. Parse the Webhook-Signature header and extract all v1 values.

  6. Compare your computed signature against each provided signature using a constant-time comparison.

  7. 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:

  1. Open the webhook endpoint.

  2. 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:

  1. Open the webhook endpoint.

  2. Click Roll secret.

  3. Choose a grace period.

  4. 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:

  1. Rotate the secret with a grace period long enough to deploy configuration changes.

  2. Update your application to use the new secret.

  3. Verify webhook deliveries are succeeding.

  4. 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-2xx HTTP responses

  • Request 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.

Did this answer your question?