# Signals webhooks

Canonical page: https://docs.ovok.com/signals/standalone/webhooks

Signals can push events to your system instead of you polling `GET /alert`. A **subscriber** is a Signals client with a webhook URL and a list of events. Signals manages the deliveries; Ovok exposes the routes to create and run subscribers, and holds their credentials.

Every route on this page needs a [Manager application](/signals/standalone#app-types) or a user with the capabilities shown.

| Task | Route | Capabilities |
| --- | --- | --- |
| Create a subscriber | [`POST /v1/signals/api/user`](/signals/standalone/api/clients/create-a-user) | `signals_webhooks:update`, `signals_events:read` |
| Change its URL, events or reach | [`POST /v1/signals/api/user/:id`](/signals/standalone/api/clients/update-a-user) | `signals_webhooks:update`, `signals_events:read` |
| Delete it | [`DELETE /v1/signals/api/user/:id`](/signals/standalone/api/clients/delete-a-user) | `signals_webhooks:delete` |
| Check its endpoint | [`GET /v1/signals/api/subscriber/:id/webhook`](/signals/standalone/api/clients/read-the-webhook-endpoint) | `signals_webhooks:read` |
| Send a test event | [`POST .../webhook/test`](/signals/standalone/api/clients/send-a-test-event) | `signals_webhooks:update` |
| Turn it off or on | [`POST .../webhook/disable`](/signals/standalone/api/clients/disable-the-webhook-endpoint), [`.../enable`](/signals/standalone/api/clients/enable-the-webhook-endpoint) | `signals_webhooks:update` |
| Rotate its signing secret | [`POST .../webhook/rotate-secret`](/signals/standalone/api/clients/rotate-the-webhook-signing-secret) | `signals_webhooks:update` |
| See what was sent | [`GET /v1/signals/api/events`](/signals/standalone/api/events/list-webhook-events), [`.../events/:id/attempts`](/signals/standalone/api/events/list-a-deliverys-attempts) | `signals_events:read` |
| Send events again | [`POST /v1/signals/api/events/redeliver`](/signals/standalone/api/events/redeliver-events), [`.../events/:id/redeliver`](/signals/standalone/api/events/redeliver-one-event) | `signals_webhooks:update` |

## Create a subscriber [#create-a-subscriber]

```bash
curl --request POST 'https://api.sandbox.ovok.com/v1/signals/api/user' \
  --user "${OVOK_MANAGER_ID}:${OVOK_MANAGER_SECRET}" \
  --header 'Content-Type: application/json' \
  --data '{
    "role": "SUBSCRIBER",
    "name": "Ward alerts",
    "webhookUrl": "https://example.com/signals/webhook",
    "events": ["alert.*"],
    "reach": "all"
  }'
```

- Add `episode.*` only once your tenant has `episodicAlerts` on; until then it answers `403` `FEATURE_DISABLED` (see [Events](#events)).
- Send `role: "SUBSCRIBER"`. Through Ovok, only subscribers are created; any other role answers `403`.
- Don't send `clientId`, `secret` or `webhookSecret`. Ovok generates the subscriber's id and Signals login, which it never uses, and answers `422` if you send them.
- Subscribers are on event schema 2. Sending another `eventSchema` answers `422`.
- The answer carries `signingSecret` **once**. Store it: you need it to verify deliveries, and you get a new one only by rotating it.
- `Location` names the new subscriber under `/v1/signals/api/user/:id`.

A create retried after a timeout can make a second subscriber. List them with [`GET /v1/signals/api/user`](/signals/standalone/api/clients/list-users) and delete the extra one.

## Reach: which patients it hears about [#reach-which-patients-it-hears-about]

| `reach` | Patients |
| --- | --- |
| `attached` (default) | Only the patients attached to it. Attach them with [`POST /v1/signals/api/subscriber/:id/patients`](/signals/standalone/api/patients/attach-patients-to-a-subscriber). |
| `all` | Every patient of your tenant, including patients enrolled later. |

## Events [#events]

Name exact events, or a whole family with a wildcard such as `alert.*`.

| Event | When |
| --- | --- |
| `alert.triggered` | A reading breaches a trigger and opens an alert. |
| `alert.refired` | Another breach refreshes an open alert. |
| `alert.resolved` | A trigger resolves the alert. |
| `alert.acknowledged` | Someone acknowledges the alert. |
| `alert.empty`, `alert.note`, `alert.inactive` | An empty reading, a note, or a patient going quiet raises an alert. |
| `episode.opened`, `episode.closed` | An episode starts or ends. |
| `observation.created`, `observation.updated` | A reading is stored or changed. One delivery per reading: subscribe only if you need them. |
| `patient.subscribed`, `patient.unsubscribed` | A patient is attached to or detached from the subscriber. |
| `patient.config`, `patient.inactive` | A patient's config changes, or the patient goes inactive. |
| `webhook.test` | You sent a test event. It is not queued, retried or listed. |
| `webhook.disabled` | Signals turned another subscriber's endpoint off because it answered `410` or kept failing. You are never told about your own. |

Readings posted with `Prefer: respond-sync` report their alerts in the answer, and send no webhook for them.

**Episode events need episodes.** Signals records episodes only while your tenant has `episodicAlerts` on. Naming an episode event while it is off answers `403` `FEATURE_DISABLED`. Turn it on with [`PATCH /v1/signals/api/tenant`](/signals/standalone/api/tenants/update-tenant-settings) and `{"settings":{"episodicAlerts":true}}`.

## Verify each delivery [#verify-each-delivery]

Deliveries follow [Standard Webhooks](https://www.standardwebhooks.com/). Each one carries `webhook-id`, `webhook-timestamp` and `webhook-signature`; verify them with a Standard Webhooks library and the subscriber's `signingSecret` (`whsec_…`). The body is one JSON event with its type, its data and when it happened. If you set `custom`, every delivery carries it back as `x-actimi-custom`.

Answer `2xx` quickly and do the work afterwards. A delivery that fails is retried.

**Rotating the secret.** [`rotate-secret`](/signals/standalone/api/clients/rotate-the-webhook-signing-secret) returns the new secret once. The old one keeps signing for 24 hours, so `webhook-signature` carries both and you can switch with no gap. Rotate again only after your receiver uses the new secret.

## When deliveries fail [#when-deliveries-fail]

| Situation | What happens |
| --- | --- |
| Recent attempts failed | The endpoint is `failing`. Signals keeps retrying. |
| Five days of failures with no delivery | Signals turns the endpoint off (`disabledReason: failing`). |
| Your endpoint answers `410 Gone` | Signals turns it off at once (`disabledReason: gone`). |
| You disabled it | Nothing is sent (`disabledReason: manual`). |

While an endpoint is off, new events are **held**, not dropped. Ovok tells your project's admins when Signals turns one off. After you fix the receiver, [enable](/signals/standalone/api/clients/enable-the-webhook-endpoint) it, then send the held events with [`POST /v1/signals/api/events/redeliver`](/signals/standalone/api/events/redeliver-events)`?status=held&since=<ISO 8601 time>`. `since` is required; the call answers `202` with how many it queued, at most 1,000 at a time. Enabling alone re-queues nothing.

[`GET /v1/signals/api/events`](/signals/standalone/api/events/list-webhook-events) lists every event with how its delivery went. It pages oldest first, and can lag a little behind the newest deliveries.

## Related [#related]

- [Signals standalone](/signals/standalone)
- [Signals route policies](/access-policies/signals)
