CompanyStack
APISamplesPricingDocs
Explore data

The register

IndustriesIndustries by SIC code: key figures, the largest companies by turnover and the newest.Towns and citiesCompanies registered in each UK town, its main industries and the newest arrivals.New incorporationsThe companies formed each day, newest first, with their industry and town.Company status guidesWhat active, dissolved, liquidation and proposal to strike off mean for you.

Look closer

Search companiesAny UK company by name or number: profile, health score and filed accounts.Financial screenerFilter companies by the figures in their latest filed accounts.Sample outputsReal reports, exports and API responses to look through.
Log inCreate free account
APISamplesPricingDocs

Explore data

IndustriesTowns and citiesNew incorporationsCompany status guidesSearch companies
Log inCreate free account
  1. Home
  2. Help centre
  3. Webhooks: get changes sent to your system

API and developers

Webhooks: get changes sent to your system

We can send each change to a company you monitor to your own HTTPS address, signed with HMAC-SHA256. How to set it up, check the signature, and handle retries.

A webhook sends each change we find on a monitored company to an address on your own system, as it happens. We check for changes every 15 minutes.

Who can use webhooks

  • API, any plan, including Free. Set webhooks on companies you watch, or on lists.
  • Web app Team. Turn on webhooks in a list's settings.

Set up a webhook

In the web app (Team): open the list, open its settings, find Monitoring and alerts, turn on Send changes to a webhook and enter the Webhook URL.

Through the API:

  • For one company: POST /watchlist with {"company_number": "00445790", "webhook_url": "https://example.com/hooks/companystack"}.
  • For a list: PATCH /lists/{id} with {"webhook_url": "https://example.com/hooks/companystack", "alert_webhook": true}.

If a company has its own webhook address, it is used instead of the list's. You get one webhook per change, even if the company is on several of your lists.

Your address must use https on port 443, with a public host name. Addresses with a username or password in them, or private network addresses, are refused.

What we send

A POST with a JSON body:

{
  "id": "evt_123456_789",
  "type": "company.alert",
  "created_at": "2026-10-01T09:15:02.000Z",
  "data": {
    "alert_id": 123456,
    "company_number": "00445790",
    "company_name": "EXAMPLE LIMITED",
    "alert_type": "gazette_notice",
    "severity": "critical",
    "title": "…",
    "detail": "…",
    "detected_at": "2026-10-01T09:15:02.000Z",
    "links": {
      "company": "https://api.companystack.co.uk/company/00445790",
      "alerts": "https://api.companystack.co.uk/watchlist/alerts?company_number=00445790"
    },
    "lists": [{ "id": 42, "name": "Suppliers" }]
  }
}

alert_type is one of: status_change, name_change, address_change, sic_change, accounts_filed, accounts_overdue, confirmation_statement_filed, confirmation_statement_overdue, charge_registered, charge_satisfied, psc_change, gazette_notice, new_filing, officer_change or financials_available. severity is info, warning or critical.

Each request has these headers:

  • CompanyStack-Signature: the signature (see below);
  • CompanyStack-Event-Id: the same as id. It stays the same when we retry, so use it to ignore duplicates;
  • CompanyStack-Event-Type: for example company.alert.

Check the signature

Every webhook is signed, so you can be sure it came from us. The header looks like this:

CompanyStack-Signature: t=1759310102,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
  • t is the time we signed it, in Unix seconds.
  • v1 is the hex HMAC-SHA256 of the text <t>.<raw request body>, using your webhook secret as the key.

Get your secret with your API key: GET /watchlist/webhook-secret. It starts with whsec_. Use the whole string, including whsec_, as the key. Web app users can call this with the API key created when they signed up, from the API keys page.

To check a request:

  1. Read the raw request body exactly as received. Do not parse and re-encode the JSON first.
  2. Take t and every v1 value from the header.
  3. Reject the request if t is more than 5 minutes from your current time.
  4. Work out HMAC-SHA256 of t + . + the raw body, with your secret, as hex.
  5. Accept the request if it matches any v1 value. Compare in constant time.

Node.js:

import crypto from 'node:crypto';

export function verifyCompanyStackSignature(rawBody, header, secret, toleranceSeconds = 300) {
  if (!header) return false;
  let timestamp = null;
  const signatures = [];
  for (const part of header.split(',')) {
    const [key, value] = part.trim().split('=', 2);
    if (key === 't' && /^\d+$/.test(value)) timestamp = Number(value);
    if (key === 'v1' && /^[0-9a-f]{64}$/.test(value)) signatures.push(value);
  }
  if (timestamp === null || signatures.length === 0) return false;
  if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`, 'utf8').digest();
  return signatures.some(sig => crypto.timingSafeEqual(Buffer.from(sig, 'hex'), expected));
}

Python:

import hashlib
import hmac
import time

def verify_companystack_signature(raw_body: bytes, header: str, secret: str, tolerance_seconds: int = 300) -> bool:
    if not header:
        return False
    timestamp = None
    signatures = []
    for part in header.split(","):
        key, _, value = part.strip().partition("=")
        if key == "t" and value.isdigit():
            timestamp = int(value)
        elif key == "v1" and len(value) == 64:
            signatures.append(value)
    if timestamp is None or not signatures:
        return False
    if abs(time.time() - timestamp) > tolerance_seconds:
        return False
    signed = f"{timestamp}.".encode() + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(expected, sig) for sig in signatures)

Rotate the secret

POST /watchlist/webhook-secret/rotate creates a new secret. For the next 24 hours we sign with both the new and the old secret, so the header carries two v1 values. Update your system within that time.

Respond quickly

Reply with any 2xx status within 10 seconds. Do slow work after you reply.

If you do not, we retry after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours and 24 hours: 8 attempts in all. After that the delivery is marked as failed.

Test and troubleshoot

  • Send a test: POST /watchlist/webhooks/test with {"url": "https://example.com/hooks/companystack"} sends a signed webhook.test event straight away. It is not retried.
  • See deliveries: GET /watchlist/webhooks/deliveries?status=failed lists recent deliveries, with pending, delivered or failed status.
  • Signature never matches? Most often the body was changed before checking, for example by a framework that parses JSON. Check the raw bytes.

Last updated 2 October 2026

Related articles

  • API keys and authentication
  • API rate limits and error codes (401, 403, 404, 429)
  • Paging through API results
  • Connect SI agents with the hosted MCP server

Still need help? Contact us, or if you have an account, open a support request.

Data from Companies House. Contains public sector information licensed under the Open Government Licence v3.0.

The same data is available through the CompanyStack API (OpenAPI spec). Company status meanings · Data freshness

CompanyStack

The UK company register, made useful. Company profiles, health scores and financials in the web app, and the same data through the CompanyStack API.

Talk to us

CompanyStack web app

  • Dashboard
  • Financial screener
  • Watchlist and alerts
  • Map
  • Web app pricing
  • Create a free account

CompanyStack API

  • UK company data API
  • API documentation
  • API explorer
  • API pricing
  • Signals add-on
  • Get a free API key
  • Enterprise and bulk data

Built for

  • Accountants and bookkeepers
  • Compliance and procurement
  • Lenders and credit
  • Sales and business development
  • Investors and corporate finance
  • Insolvency and business recovery
  • Developers and data teams
  • Recruitment agencies

See the data

  • Sample outputs
  • Data sources
  • Book a 20-minute call
  • Monthly UK company report
  • UK private company data
  • Company status meanings
  • SIC codes list
  • Compare providers

© 2026 CompanyStack.

TermsPrivacyCookiesRefundsLegalBrand and pressAboutHelp centreContactDocs