SIGNALS Documentation
API Reference

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
member.vat_validated A member's VAT number is validated (VIES or manual confirmation)

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

Containers

Event Trigger
container.created A container is created (product-backed or temporary)
container.item.packed An item is packed into a container
container.item.unpacked An item is unpacked from a container
container.item.transferred An item is moved between containers in one step
container.nested A container is packed inside another container
container.sealed A container passes the seal gate and is sealed
container.unsealed A sealed container is reopened (manual or transfer-triggered)
container.dissolved A container is manually or dispatch-dissolved
container.dispatched A kit/hybrid container is dispatched as a persistent unit
container.returned A kit/hybrid container is returned from an opportunity
container.repack.started Repack-on-return initiated for a transport container housing
container.repack.item An item is returned and repacked during a repack session

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

Opportunities

Opportunity events fire on every committed mutation of an opportunity, its line items, costs, allocated assets, and quote versions. They are never re-delivered when the event store is rebuilt.

Event Trigger
opportunity.created A new opportunity (quote) is created
opportunity.updated An opportunity's header fields are updated
opportunity.quoted An opportunity is marked as quoted
opportunity.converted_to_order A quote is converted to an order
opportunity.reverted_to_quotation An order is reverted back to a quotation
opportunity.reverted_to_draft A quotation is reverted back to a draft
opportunity.reinstated A lost, dead, postponed, or cancelled opportunity is reinstated
opportunity.reopened A completed order is re-opened back to an active order
opportunity.locks_applied FX/tax locks are applied to an order
opportunity.locks_released An order's FX/tax locks are released for re-pricing
opportunity.status_changed An opportunity's status changes
opportunity.status_promoted An opportunity's status is promoted along its workflow
opportunity.cloned An opportunity is cloned into a new opportunity
opportunity.deleted An opportunity is soft-deleted
opportunity.restored A soft-deleted opportunity is restored
opportunity.deal_price_set A negotiated deal total is set
opportunity.deal_price_cleared A negotiated deal total is cleared
opportunity.group_deal_price_set A negotiated deal total is set on a line-item group
opportunity.group_deal_price_cleared A negotiated deal total is cleared from a line-item group
opportunity.pricing_recalculated Opportunity pricing is recalculated (rates, tax, surcharges)
opportunity.discount_reset Applied discounts are cleared from an opportunity
opportunity.discount_category_applied A discount category is applied to an opportunity
opportunity.discount_manually_overridden A discount category percentage is manually overridden
opportunity.item_added A line item is added
opportunity.item_details_updated A line item's details (description, notes, etc.) are updated
opportunity.item_renamed A line item is renamed
opportunity.item_removed A line item is removed
opportunity.item_quantity_changed A line item's quantity changes
opportunity.item_dates_changed A line item's hire dates change
opportunity.item_discount_set A line item discount is set
opportunity.item_optional_toggled A line item is toggled optional/required
opportunity.item_price_overridden A line item unit price is overridden
opportunity.item_substituted A line item product is substituted
opportunity.item_surcharge_overridden A line item surcharge amount is manually overridden
opportunity.items_restructured An opportunity's line-item tree is restructured
opportunity.items_reordered An opportunity's line items are reordered
opportunity.surcharge_group_toggled A surcharge group is enabled or disabled on an opportunity
opportunity.cost_added An additional cost is added
opportunity.cost_updated An additional cost is updated
opportunity.cost_removed An additional cost is removed
opportunity.version_created A new quote version is created
opportunity.version_activated A quote version is made active
opportunity.version_accepted A quote version is accepted by the customer
opportunity.version_declined A quote version is declined
opportunity.version_sent A quote version is sent to the customer
opportunity.version_relabelled A quote version's label is changed
opportunity.version_superseded A quote version is superseded by a newer one
opportunity.version_deleted A quote version is deleted
opportunity.asset_allocated A serialised asset is allocated to a line item
opportunity.asset_deallocated A serialised asset is deallocated
opportunity.asset_substituted An allocated asset is substituted for another
opportunity.asset_prepared An asset is marked prepared for dispatch
opportunity.asset_preparation_reverted An asset's prepared state is reverted
opportunity.asset_checked An asset is scan-checked
opportunity.asset_dispatched An asset is dispatched
opportunity.asset_on_hire An asset is marked on-hire
opportunity.asset_returned An asset is returned
opportunity.asset_status_reverted An asset's lifecycle status is reverted
opportunity.asset_container_set An asset is placed in a container
opportunity.asset_container_cleared An asset is removed from its container
opportunity.bulk_dispatched A bulk (non-serialised) quantity is dispatched
opportunity.bulk_returned A bulk quantity is returned
opportunity.bulk_quantity_adjusted A bulk dispatched/returned quantity is adjusted

Documents

Event Trigger
document.created A document is generated from a template
document.pdf_generated A PDF is generated and stored for a document
document.finalised A document is finalised (locked for sharing)
document.voided A finalised document is voided
document.emailed A document is emailed to a recipient (Notification Engine, P38)
document.public_link_created A public sharing link is created for a document
document.public_link_accessed A public sharing link is accessed for the first time
document.public_link_revoked A public sharing link is revoked
document.public_link_configured A public link's expiry or PIN settings are changed
document_template.created A document template is created or duplicated
document_template.updated A document template is saved
document_template.deleted A custom document template is deleted
document_template.restored A system template is restored to its shipped default
document_template.set_as_default A template is marked as the default for its type
document_sequence.reset A document numbering sequence rolls over to a new period

Invoices

Event Trigger
invoice.created A draft invoice is generated
invoice.issued A draft invoice is issued to the customer
invoice.voided An invoice is voided
invoice.overdue An issued invoice passes its due date with an outstanding balance
payment.recorded A payment is recorded against an invoice — payload embeds payment fields (see Invoices API)
payment.refunded A refund is recorded against an invoice — payload embeds payment/refund fields (see Invoices API)
credit_note.issued A credit note is issued against an invoice

Availability

Event Trigger
availability.changed A product/store's availability read model has been recalculated

Shortages

Event Trigger
shortage.detected A shortage is detected for an order line
shortage.cleared A previously-detected shortage no longer exists
shortage.acknowledged A shortage is acknowledged at a confirmation gate
shortage.resolution.created A shortage resolution is created
shortage.resolution.confirmed A shortage resolution is confirmed
shortage.resolution.in_progress A shortage resolution's fulfilment has begun
shortage.resolution.fulfilled A shortage resolution completes and stock is available
shortage.resolution.failed A shortage resolution attempt fails
shortage.resolution.cancelled A shortage resolution is cancelled
shortage.waitlist.created A waitlist monitor is placed on a shortage
shortage.waitlist.matched Monitored stock becomes available
shortage.waitlist.expired A waitlist monitor expires without matching
shortage.escalated An unresolved shortage passes the configured escalation threshold

Purchase Orders

Event Trigger
purchase_order.created A draft purchase order is created
purchase_order.updated A draft purchase order is updated
purchase_order.sent A purchase order is sent to the supplier
purchase_order.received Goods are received against a purchase order
purchase_order.cancelled A purchase order is cancelled
purchase_order.deleted A draft purchase order is deleted

Virtual Stock

Event Trigger
virtual_stock_intake.created A draft virtual stock intake is created
virtual_stock_intake.updated A draft virtual stock intake is updated
virtual_stock_intake.confirmed A virtual stock intake is confirmed
virtual_stock_intake.received Virtual stock is received into availability
virtual_stock_intake.returned Virtual stock is returned after the hire window
virtual_stock_intake.cancelled A virtual stock intake is cancelled
virtual_stock_intake.deleted A draft virtual stock intake is deleted

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 API Console Webhooks page by clicking the logs icon on a webhook row. Deliveries also appear in the unified Request Log on the webhook_sent channel.

Outbound metering weights

Each outbound event carries a price weight (0–4) declared in WebhookEventRegistry. Weights default to 1 unless overridden at registration time. On the first delivery attempt of each job run, DeliverWebhook records the weight on the webhook_logs row and increments the webhook_sent daily rollup by the event's weight.

Weight 0 events are still logged but contribute zero weighted units toward usage totals.

Inbound webhook receiver

Signals can accept webhook POSTs from external systems via registered inbound sources. Manage sources from the API Console at /api/webhooks (Inbound tab) or programmatically when that surface is exposed.

Receive URL

POST {your-app-url}/webhooks/receive/{slug}

Replace {slug} with the source's URL slug. The route is public (no Sanctum token) — authentication is HMAC signature verification only.

Signature verification

Each source stores a secret, signature algorithm (default sha256), and header name (default X-Signature). The middleware computes:

$expected = hash_hmac($source->signature_algo, $requestBody, $source->secret);
$provided = $request->header($source->signature_header);

$valid = is_string($provided) && hash_equals($expected, $provided);

Send the HMAC digest as a plain string in the configured header (not prefixed with sha256=).

Responses

Status Condition Body
200 Valid signature {"status":"received"} — fires InboundWebhookReceived
401 Missing or invalid signature {"message":"Invalid signature"} — logged with weight 0
404 Unknown slug or inactive source Laravel 404 page

Inbound metering weights

Unlike outbound events (code-defined in WebhookEventRegistry), inbound weights are stored per source in inbound_webhook_sources.price_weight (0–4, editable in the console). Valid requests meter on the webhook_received channel; invalid signatures log detail rows but do not increment weighted units.

Inbound logs

Receipts are stored in inbound_webhook_logs and surfaced in the Request Log (webhook_received channel) and the source's logs modal in the API Console.