---
title: Change Notifications
description: Change notifications post an event to a URL you choose whenever a snippet is created or its code changes. Use them to review new code, keep an audit trail outside WordPress, or post to a chat channel through a service…
url: "https://brightleafdigital.io/docs/change-notifications/"
updated: "2026-10-22"
category: GravityOps Code Snippet Manager / Advanced
---

# Change Notifications

Change notifications post an event to a URL you choose whenever a snippet is created or its code changes. Use them to review new code, keep an audit trail outside WordPress, or post to a chat channel through a service such as Zapier, Make, or n8n.

## Setting it up

1. Go to **Settings** and find **Change notifications**.
2. Enter the **webhook URL** that should receive the events.
3. Enter a **signing secret**. The receiving end uses it to check that a request really came from your site. The field stays blank after saving; leave it blank to keep the saved secret, or type a new one to replace it.
4. Choose the events to send, then save.

Nothing is sent until both a URL and a secret are saved. Clear the URL and save to turn notifications off.

## Options

| Option | Default | What it does |
| --- | --- | --- |
| A snippet is created | On | Sends a `created` event for each new snippet. |
| A snippet's code changes | On | Sends a `code_changed` event when a snippet's code is different after a save, update, or revision restore. Renaming a snippet, changing its tags, or activating it does not send an event. |
| Include snippets created by an import | Off | JSON imports and imports from the Code Snippets plugin can create many snippets at once, so they are left out unless you check this. Snippets added by a GitHub collection are always included. |
| Ignore changes made by | Empty | Usernames or email addresses, one per line, whose changes are not sent — for example your own, so you are only told about changes other people make. Matching ignores letter case. Changes made through the Abilities API count as the user the request authenticated as. Scheduled updates that run without a signed-in user are always sent. |

## Multisite

On a multisite network, a network administrator can set **Applies to** to **Every site in this network** on the main site's Settings tab. One webhook then covers every site in the network, including sites added later, and site administrators cannot turn it off. A site can still add a webhook of its own; both receive the site's events. A site webhook with the same URL as the network webhook is skipped, so the endpoint does not receive each event twice.

Choosing one scope on the main site removes the other, so the main site keeps a single webhook.

## The request

Each event is a `POST` with a JSON body:

```
{
  "event": "created",
  "occurred_at": "2026-10-22T14:03:11+00:00",
  "origin": "save",
  "site": { "id": 3, "name": "Example Site", "url": "https://example.com/", "network_url": "https://example.com/" },
  "snippet": {
    "id": 42,
    "name": "Hide the admin bar",
    "previous_name": null,
    "description": null,
    "type": "php",
    "status": "inactive",
    "collection_id": 0,
    "version": null,
    "edit_url": "https://example.com/wp-admin/admin.php?page=..."
  },
  "user": { "id": 7, "login": "jane", "email": "jane@example.com", "display_name": "Jane" }
}
```

`origin` says how the change happened, using the origins listed in [Actions and filters](https://brightleafdigital.io/docs/actions-and-filters/). `user` is `null` when no one was signed in, such as during a scheduled update. The snippet's code is never included; follow `edit_url` to read it.

These headers are sent with each request:

| Header | Value |
| --- | --- |
| `X-GOCSM-Event` | `created` or `code_changed`. |
| `X-GOCSM-Delivery` | A unique ID for the event, kept the same across retries. |
| `X-GOCSM-Timestamp` | The Unix time the request was signed. |
| `X-GOCSM-Signature` | `sha256=` followed by the hex HMAC-SHA256 of the timestamp, a period, and the raw body, keyed with your signing secret. |

## Verifying a request

Recompute the signature from the raw body and compare it in constant time. Reject requests whose timestamp is more than a few minutes old so a captured request cannot be replayed.

```
$body      = file_get_contents( 'php://input' );
$timestamp = $_SERVER['HTTP_X_GOCSM_TIMESTAMP'] ?? '';
$expected  = 'sha256=' . hash_hmac( 'sha256', $timestamp . '.' . $body, $secret );

if ( ! hash_equals( $expected, $_SERVER['HTTP_X_GOCSM_SIGNATURE'] ?? '' ) || abs( time() - (int) $timestamp ) > 300 ) {
    http_response_code( 401 );
    exit;
}
```

## Delivery and retries

Events are sent at the end of the request that made the change, so saving a snippet does not wait for them. Any `2xx` response counts as delivered. Anything else is retried after 5 minutes and again after 30 minutes. Each failure is recorded in the **Sync Log** as `webhook_failed`: a warning while retries remain, and an error after the last attempt.

Retries run on WP-Cron. If your site has little traffic and no server cron, a retry can be later than scheduled.

The URL must resolve to a public address. Requests to local or private network addresses are refused, which shows up in the Sync Log as a failed delivery.

## Developer hooks

`gocsm_webhook_should_send` filters whether an event is sent (`bool $send`, `array $payload`, `string $scope`, `Snippet $snippet`). `gocsm_webhook_payload` filters the payload before it is signed (`array $payload`, `string $scope`, `Snippet $snippet`). `$scope` is `network` or `site`. See [Actions and filters](https://brightleafdigital.io/docs/actions-and-filters/).
