Webhooks
Test and manage
Every action in the webhook row menu, the Deliveries sheet, pause and resume, replay, validation and server messages, roles and the public webhook management API.
Everything lives in Developers → Webhooks. Each webhook is one row. You see its status, its filters and when it last delivered.
Who can do what:
- Org admins and managers can add, edit, pause, delete, rotate and replay. Other members see the message "Only org admins and managers can manage webhooks."
- You can have 5 webhooks per organization. At the limit the page says "You can have up to 5 webhooks. Delete one to add another."
- A row shows Active, Paused or Disabled.
Row menu
Click Webhook actions on a row.
| Action | What it does |
|---|---|
| Send test event | Queues a test run webhook with reviews.new at total: 1. Its token returns one sample G2 review. It carries Signals-Test: true, is signed and goes through the normal path, so it arrives within a minute. Active webhooks only. One test can wait at a time. |
| View deliveries | Opens the Deliveries sheet (below). |
| Edit | Change the URL and the filters. The signing secret stays the same. |
| Rotate secret | Creates a new secret and shows it once. The old secret keeps signing for 24 hours, so requests carry two v1= values. See Verify signatures. |
| Pause | Stops sending. Deliveries that were waiting stay waiting. A request already in flight still completes. |
| Resume | Starts sending again, from where it stopped: runs that finished while the webhook was paused are sent, oldest first. |
| Delete | Removes the webhook and its delivery log for good. Nothing more is sent, also not deliveries that were waiting. Its page tokens stop working (404). The confirmation reads: "Signals stops sending events to (your URL) and its delivery log is deleted. This can't be undone." |
For a Disabled webhook the banner offers Re-enable. Like Resume, it continues where it stopped, and it queues the dead deliveries again. See Retries and failures.
Last delivered does not refresh itself
The Last delivered time updates when you reload the page.
Edit filters
Changes apply to runs sent after the change. Runs already sent keep the counts in their POST, but their pages apply the current filters: after an edit, a page's total and events can differ from those counts. If you remove a project from the filter, its runs stop. If a project in the filter was deleted, the row shows a Deleted project pill and that entry matches nothing.
Deliveries sheet
View deliveries lists the delivery log, newest first. One row is one POST: one run for one webhook. The log keeps 30 days.
Each row shows:
| Item | Shows |
|---|---|
| Status | Pending, Sending, Delivered, Failed, Dead or Replayed. See Status labels |
| Run | Run and a short run id, with the counts per event name, for example "3 events". A test delivery shows a Test tag and Sample review. A replayed row shows the run of the new delivery that replaced it. Deliveries from before run webhooks show no run and no counts |
| Attempt | Attempt N of 9 |
| Duration | The last attempt's time, N ms |
| HTTP status | The status code your server returned |
| Error | The failure label when it failed |
Buttons: Resend on a failed row (the same delivery again, same webhookId), Replay on a dead row (a new delivery with a new webhookId), Replay dead at the top (every dead delivery, not failed ones), Load more. The footer reads "Showing N of M deliveries", where M is every delivery of the webhook. When the total is unknown it reads "Showing N deliveries". An empty log reads "No deliveries yet. Send a test event to see one here."
Resend and Replay need an active webhook. The message "This webhook isn't active. Resume it first." appears otherwise. See Replay.
Messages
URL validation
The dialog checks the URL as you type.
| Message | Cause |
|---|---|
| Enter the URL to send events to. | The field is empty. |
| This URL is too long. | More than 2048 characters. |
| Enter a full URL, like https://your-app.com/signals-hook. | Not a valid URL. |
| The URL must start with https://. | http:// or another scheme. |
| Use the default https port (443). | A custom port. |
| Remove the username and password from the URL. | Credentials in the URL. |
| Use a hostname, not an IP address. | An IPv4 or IPv6 address. |
| Use a public hostname. localhost and internal names can't receive webhooks. | localhost, a name without a dot, or .local, .internal, .lan and similar. |
| Wildcard DNS services like nip.io or sslip.io can't receive webhooks. Use your own hostname. | nip.io, sslip.io, xip.io, localtest.me, lvh.me. |
Server messages
| Message | Cause |
|---|---|
| You already have a webhook for this URL. | Another webhook in your org has the same URL. |
| You can have up to 5 webhooks. Delete one to add another. | The limit is reached. |
| This webhook isn't active. Resume it first. | Test, resend or replay on a paused or disabled webhook. |
| A test event is already on its way. Wait for it to finish before sending another. | A test is still pending. |
| One of the chosen projects no longer exists. Remove it from the Projects filter. | A project in the filter was deleted. |
| Couldn't load more deliveries. Reopen the log. | Load more failed. |
Public webhook management API
You can manage webhooks without the app. The same API key authenticates, sent in the x-api-key header. Send test event, Rotate secret and the delivery log are app-only.
| Method and path | Does |
|---|---|
GET /api/public/v1/webhooks | List up to 5 webhooks, oldest first. Secrets are never returned. Response { "items": [...] }. |
POST /api/public/v1/webhooks | Create. Body { "url": "...", "filters": {...} }. Returns 201 with the webhook and its secret, once. |
PATCH /api/public/v1/webhooks/{id} | Change url, filters and/or status (active or paused). At least one field. With status: "active", resume picks where sending continues (below). |
POST /api/public/v1/webhooks/{id}/replay | Queue deliveries again. Body below. |
DELETE /api/public/v1/webhooks/{id} | Delete the webhook and its delivery log. Returns { "deleted": true }. |
filters has five lists: eventTypes, subEventTypes, actions (new, updated, removed), domains (lowercase competitor domains) and projectIds. An empty or missing list matches everything.
resume on PATCH takes one of two values:
| Value | Sends |
|---|---|
where_it_stopped (default) | Every run that finished since the last one sent, oldest first. Dead deliveries are queued again. |
now | Only runs that finish from now on. Runs that finished while it was paused or disabled are skipped. |
The replay body picks what to queue again. Every key is optional, and the keys you send are combined with AND:
| Key | Narrows to |
|---|---|
status | dead (the default) or failed. Only deliveries with this status are replayed. |
deliveryIds | These deliveries (string[], up to 100). They must also have the status above, so send "status": "failed" to replay failed ones. |
runId | The delivery of one run. |
since | Deliveries created at or after this ISO 8601 time. |
An empty body {} replays every dead delivery. Test deliveries are never replayed. One call replays at most 500 deliveries, oldest first; call again until replayed is 0. The response is { "replayed": n }.
A replay is a new delivery with a new webhookId and fresh page tokens. The old row turns Replayed.
Set SIGNALS_HOST to the address of the app you sign in to. The Try it command on the Developers page shows it.
1curl -X POST $SIGNALS_HOST/api/public/v1/webhooks \2 -H "x-api-key: $SIGNALS_API_KEY" \3 -H "Content-Type: application/json" \4 -d '{5 "url": "https://your-app.com/signals-hook",6 "filters": { "eventTypes": ["jobs"], "subEventTypes": [], "actions": ["removed"], "domains": ["hubspot.com"] }7 }'
1curl -X POST $SIGNALS_HOST/api/public/v1/webhooks/$WEBHOOK_ID/replay \2 -H "x-api-key: $SIGNALS_API_KEY" \3 -H "Content-Type: application/json" \4 -d '{ "status": "dead" }'
| Status | Meaning |
|---|---|
201 / 200 | Created / OK. |
400 | Invalid body: URL rules, filters, a projectIds entry that does not exist, or a replay body with an unknown key or a bad value. |
401 | Missing or invalid API key. |
403 | The key does not belong to a Signals organization. |
404 | No such webhook in your organization (PATCH, DELETE, replay), or Signals is switched off. |
409 | Another webhook already has this URL, or a replay on a webhook that is not active. |
422 | You already have 5 webhooks (POST). |
429 | More than 30 requests per minute from one IP. Retry after 60 s. |
The API errors have the form { "error": "..." }. The full machine-readable spec is at /api/public/docs.