How do API webhooks work?
On this page
A webhook lets UtilitySEO tell your server when a scan finishes or fails, instead of your software asking over and over. Set one per project with the API, using PUT /webhook and an https address. We then send a short message to that address within about a minute of each scan ending, signed so you can check it came from us.

Set it up
Send this with your API key (see the UtilitySEO API):
PUT /api/v1/webhook
{ "url": "https://dashboard.example.com/hooks/utilityseo", "enabled": true }
- The address must be https, on the standard port, and on the public internet. Private and internal addresses are refused, and checked again before each delivery.
- The answer includes a signing secret. It is shown only then: store it safely on your server.
GET /webhookshows the webhook and its last delivery, never the secret.DELETE /webhookremoves it.- Send
"rotateSecret": trueto get a new secret. A new one is also issued when the address changes.
What we send
For any scan of the project, started in the app or through the API, that finishes or fails:
{ "event": "scan.finished", "scanId": "site_812", "projectId": 27, "type": "site", "score": 78, "finishedAt": "..." }
event is scan.finished or scan.failed (then score is empty). Use scanId to fetch the results.
Check it came from us
Each message carries an X-UtilitySEO-Signature header: t= a time in seconds, and v1= a signature. To check it:
- Take the raw body exactly as received, before reading it as JSON.
- Work out an HMAC-SHA256 of the time, a full stop, then the body, using your signing secret.
- Compare it with
v1. - Refuse the message if the time is more than 5 minutes from your clock.
Because the time is inside the signature, a copied message cannot be replayed later. Your developer can ask support@utilityseo.com for ready-made example code.
Answer, retries and switching off
- Answer with any 2xx status within 10 seconds. Redirects are not followed.
- Anything else is tried again after 1, 5 and 30 minutes.
- After 3 deliveries in a row fail every retry, the webhook is switched off. Send
PUT /webhookwith"enabled": trueto turn it back on. - A message can occasionally arrive twice. The
X-UtilitySEO-Deliveryheader is the same on every retry, so use it, orscanId, to ignore a repeat.
Questions people ask
Can I have more than one webhook?
One per project. To send to several places, have your server pass the message on.
Can I set a webhook from the app?
Not at the moment. Webhooks are set through the API.
How long do you keep delivery records?
Delivery records are kept for 90 days.