Webhooks
Subscribe to real-time event notifications delivered to your server via HTTPS.
Overview
Webhooks let you receive real-time notifications when events happen in Signals. Register an endpoint URL, choose which events to subscribe to, and Signals will send a signed HTTP POST to your server whenever those events occur.
Managing Webhooks
Webhooks can be managed via the API (/api/v1/webhooks) or from the admin panel at Admin > Settings > Webhooks.
Events
Members
| Event | Trigger |
|---|---|
member.created |
A new member is created |
member.updated |
A member's details are updated |
member.archived |
A member is archived |
member.restored |
An archived member is restored |
member.deleted |
A member is permanently deleted |
member.merged |
Two members are merged (secondary archived into primary) |
member.anonymised |
A member's personally identifiable information is erased |
Products
| Event | Trigger |
|---|---|
product.created |
A new product is created |
product.updated |
A product's details are updated |
product.archived |
A product is archived (soft-deleted, recoverable) |
product.restored |
An archived product is restored |
product.deleted |
A product is deleted |
product.merged |
Two products are merged |
product_group.created |
A new product group is created |
product_group.updated |
A product group is updated |
product_group.deleted |
A product group is deleted |
Stock
| Event | Trigger |
|---|---|
stock_level.created |
A stock level record is created |
stock_level.updated |
A stock level is updated |
stock_level.deleted |
A stock level is deleted |
stock_transaction.created |
A stock transaction is recorded |
stock_transaction.deleted |
A stock transaction is deleted |
Activities
| Event | Trigger |
|---|---|
activity.created |
A new activity is created |
activity.updated |
An activity is updated |
activity.deleted |
An activity is deleted |
activity.completed |
An activity is marked as completed |
Rate Definitions
| Event | Trigger |
|---|---|
rate_definition.created |
A rate definition is created |
rate_definition.updated |
A rate definition is updated |
rate_definition.deleted |
A rate definition is deleted |
product_rate.created |
A product rate is assigned |
product_rate.updated |
A product rate is updated |
product_rate.deleted |
A product rate is removed |
Tax
| Event | Trigger |
|---|---|
tax_rate.created |
A tax rate is created |
tax_rate.updated |
A tax rate is updated |
tax_rate.deleted |
A tax rate is deleted |
tax_rule.created |
A tax rule is created |
tax_rule.updated |
A tax rule is updated |
tax_rule.deleted |
A tax rule is deleted |
Users
| Event | Trigger |
|---|---|
user.created |
A new user is invited or created |
user.updated |
A user's profile or roles are updated |
user.deactivated |
A user account is deactivated |
user.deleted |
A user is deleted |
Roles
| Event | Trigger |
|---|---|
role.created |
A new role is created |
role.updated |
A role's name or permissions are modified |
role.deleted |
A role is deleted |
Settings
| Event | Trigger |
|---|---|
settings.updated |
Application settings are changed |
Payload Format
Every webhook delivery sends a JSON POST request with the event name and associated data:
{
"event": "user.created",
"timestamp": "2026-01-15T14:30:00Z",
"data": {
"user": {
"id": 42,
"name": "Jane Smith",
"email": "jane@example.com"
}
}
}
Payload Schemas
Most events embed the affected resource under a singular key inside data, using the same shape the REST API returns. Lifecycle events that only need to identify a record (deletions, archive/restore) send just an id.
Tax Rate Events
tax_rate.created and tax_rate.updated include the full tax rate object:
{
"event": "tax_rate.created",
"timestamp": "2026-01-15T14:30:00Z",
"data": {
"tax_rate": {
"id": 1,
"name": "UK Standard",
"description": "Standard rate VAT",
"rate": "20.0000",
"is_active": true,
"created_at": "2026-01-15T14:30:00Z",
"updated_at": "2026-01-15T14:30:00Z"
}
}
}
| Field | Type | Description |
|---|---|---|
id |
integer | Tax rate identifier |
name |
string | Display name (e.g. UK Standard) |
description |
string|null | Optional description |
rate |
string | Percentage as a decimal string (e.g. 20.0000) |
is_active |
boolean | Whether the rate is active |
created_at / updated_at |
string | ISO 8601 UTC timestamps |
tax_rate.deleted sends only the identifier:
{
"event": "tax_rate.deleted",
"timestamp": "2026-01-15T14:30:00Z",
"data": { "id": 1 }
}
Tax Rule Events
tax_rule.created and tax_rule.updated include the full tax rule object:
{
"event": "tax_rule.created",
"timestamp": "2026-01-15T14:30:00Z",
"data": {
"tax_rule": {
"id": 5,
"organisation_tax_class_id": 1,
"product_tax_class_id": 2,
"tax_rate_id": 1,
"priority": 10,
"is_active": true,
"created_at": "2026-01-15T14:30:00Z",
"updated_at": "2026-01-15T14:30:00Z"
}
}
}
| Field | Type | Description |
|---|---|---|
id |
integer | Tax rule identifier |
organisation_tax_class_id |
integer | Organisation tax class matched by the rule |
product_tax_class_id |
integer | Product tax class matched by the rule |
tax_rate_id |
integer | Tax rate applied when the rule matches |
priority |
integer | Higher priority rules win when several match |
is_active |
boolean | Whether the rule is active |
created_at / updated_at |
string | ISO 8601 UTC timestamps |
tax_rule.deleted sends only the identifier:
{
"event": "tax_rule.deleted",
"timestamp": "2026-01-15T14:30:00Z",
"data": { "id": 5 }
}
Product Archive & Restore Events
product.archived and product.restored are lifecycle transitions on an existing product and carry only its identifier. Fetch the current state from GET /api/v1/products/{id} if you need the full record.
{
"event": "product.archived",
"timestamp": "2026-01-15T14:30:00Z",
"data": { "id": 42 }
}
| Field | Type | Description |
|---|---|---|
id |
integer | Product identifier |
Verifying Signatures
Every delivery includes an X-Signals-Signature header containing an HMAC-SHA256 signature of the request body. Verify it against the secret you received when creating the webhook:
$signature = hash_hmac('sha256', $requestBody, $webhookSecret);
if (! hash_equals($signature, $request->header('X-Signals-Signature'))) {
abort(401, 'Invalid signature');
}
Retry Behaviour
Failed deliveries (non-2xx response or connection timeout) are retried with exponential backoff up to 6 times over approximately 12 hours. After 3 consecutive days of failures, the webhook is automatically disabled.
Re-enable a disabled webhook from the admin panel or by updating it via the API with is_active: true.
Delivery Logs
Every delivery attempt is logged. View logs via the API at GET /api/v1/webhooks/{id}/logs or from the admin panel by clicking the logs icon on a webhook row.