Monitoring System
How It Works
Alerts sent via email or webhooks are notifications dispatched according to a chosen frequency. Synchronizations must be configured through the data type sync settings to enable the completion of the events defined in the alert.
The synchronization frequency must align with the alert frequency to ensure proper functionality.
Learn more about sync settings here.
When an event is configured and triggered, a summary email is sent based on the email frequency. The email includes a section for each user.
Once alerts are defined based on the selected events and sync settings are configured accordingly, users can be assigned to alerts.
Alerts can be restricted with filters on the fields of the monitored data type — for example, only legal notices whose source is Observations RNE, or only collective procedures where an item of the administrators array matches a given type, name or city. Filters on sub-fields of array fields are supported. Ask a Qard staff member to configure them on your alert.
Key Steps:
Alert creation must be coordinated with a Qard staff member.
A list of configured alerts can be retrieved using the endpoint:
GET/api/v6/notificationExample response:
["notif_name_1", "notif_name_2"]To assign a user to an alert, use the following endpoint:
PATCH/api/v6/users/{userId}Example request body:
{
"name": "string",
"group": "default",
"notifications": []
}Add the name of the configured alert to the
notificationsfield to associate the user with the alert.
Webhook Delivery
Every alert whose url is set is delivered as a single POST request.
When a request is sent
Pending events are collected every 5 minutes. On each pass, and for each alert, one request is sent containing everything that has accumulated since the previous send, grouped by user, then by data type. Whether a request is due depends on the alert frequency:
| Frequency | A request is sent |
|---|---|
NOW | on every pass, as soon as at least one event is pending (so at most every 5 minutes) |
DAILY | once a day |
WEEKLY | once a week |
MONTHLY | once a month |
There is no "grouped" and "ungrouped" mode: the body always has the same shape. What varies is how many companies and how many events fell into the window — one company changed since the last send means one entry, ten companies mean ten entries. A very large notification is the only case where more than one request is sent; see Large notifications are split.
Request
POST https://your-app.example.com/hooks?notificationName=<alert id>&date=<unix timestamp>&nonce=<nonce>
User-Agent: Qard API
X-Webhook-Signature: sha256=<base64 hmac_sha256(secret, requestUrl)>
X-Qard-Signature: t=<unix timestamp>,v1=<base64 hmac_sha256(secret, "<t>.<raw body>")>
Two signatures are sent, both computed with your webhook secret:
| Header | Covers | Use |
|---|---|---|
X-Webhook-Signature | the complete URL, query arguments included | historical, same rule as the lifecycle Webhooks |
X-Qard-Signature | the timestamp and the body | prefer this one: it proves the content, not only the address |
Verifying the body signature, on the raw body, before any JSON parsing:
const [stamp, signature] = request.get("X-Qard-Signature").split(",");
const timestamp = stamp.slice(2); // t=1789034891
const received = signature.slice(3); // v1=<base64>
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("base64");
const valid =
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected)) &&
Math.abs(Date.now() / 1000 - Number(timestamp)) < 300; // reject replays
Retries
A delivery that does not answer with a 2xx or 3xx is sent again, with an increasing delay:
1 min, then 5, 10, 15, 30, 45, 60 min, 2 h, 8 h and 8 h. When the last attempt fails the alert is
disabled and has to be reactivated.
Every attempt carries the same notification_id: use it as an idempotency key and ignore a body
you have already processed. Acknowledge quickly with any 2xx and process asynchronously — the delay
of your endpoint is the delay of the delivery.
Large notifications are split
A body larger than 1 MB is delivered as several requests, cut company by company. The parts share one
batch_id and are numbered by part / parts; each one is self-contained, has its own
notification_id and its own retries.
The parts have no delivery order: they may arrive out of order, or at the same time. Process each
one on its own, and regroup on batch_id only if you really need the whole set.
Webhook Structure
An envelope, then a flat list of events. Everything you need sits on the event itself.
{
"notification_id": "9f1c4b0e-1d2a-4c77-8a0e-6b5c2d3e4f50",
"batch_id": "7a20cd91-0f3b-4d18-9f2e-1c7a5b6d8e90",
"part": 1,
"parts": 1,
"sent_at": "2026-09-10T08:50:03+00:00",
"alert": "annonces-legales",
"events": [
{
"kind": "add",
"data_type": "LEGAL_NOTICE",
"user_id": "803eb4fb-2589-4e48-b91f-e6d3b9fdba43",
"siren": "449727205",
"name": "ARIANA PHARMACEUTICALS (449727205)",
"id": "2b4690e8-0b28-4b97-a3d9-ce9fa536b7b9",
"object": { "date": "2026-09-08", "source": "Observations RNE", "category": "Redressement judiciaire", "…": "…" },
"updates": []
}
]
}
Envelope
| Field | Description |
|---|---|
notification_id | Identifies this delivery. Stable across retries — your idempotency key. |
batch_id | Shared by every part of one notification. Equal to a single delivery when parts is 1. |
part / parts | Position of this part and how many there are. 1 / 1 when nothing was split. |
sent_at | When the notification was built, ISO 8601. |
alert | Name of the alert, as returned by GET /api/v6/notification. |
events
One entry per event, with everything needed on the entry itself.
| Field | Description |
|---|---|
kind | add, update or delete. |
data_type | LEGAL_NOTICE, COLLECTIVE_PROCEDURE, OFFICER… |
user_id | The monitored user, the same id used everywhere in the API. |
siren | SIREN of the company, null for a natural person. |
name | Company name and SIREN, or first and last name. |
id | Id of the object that changed, the one the API serves for that object. |
object | The object itself, in the same format as its data type endpoint. |
updates | The fields that changed, as {"before": …, "after": …} pairs. Always [] on an add or a delete. |
Bodies delivered before September 2026 carried a different shape, with an event name and a users
array holding the changes keyed by data type. It is no longer sent.
Examples
The four combinations below are real notifications, shortened for readability.
One event, one company
{
"alert": "annonces-legales", "part": 1, "parts": 1,
"events": [
{ "kind": "add", "data_type": "LEGAL_NOTICE", "siren": "449727205",
"name": "ARIANA PHARMACEUTICALS (449727205)", "id": "2b4690e8-…",
"object": { "date": "2026-09-08", "category": "Redressement judiciaire", "…": "…" }, "updates": [] }
]
}
Several events, one company
{
"alert": "collectives-procedures", "part": 1, "parts": 1,
"events": [
{ "kind": "update", "data_type": "COLLECTIVE_PROCEDURE", "siren": "891675829", "name": "FLEXIFAI (891675829)",
"id": "33b4b51a-…", "object": { "…": "…" },
"updates": [ { "after": { "administrators": [ { "type": "MANDATAIRE_JUDICIAIRE", "name": "Selarl Garnier-Guillouet…", "city": "Meaux" } ] },
"before": { "administrators": null } } ] },
{ "kind": "update", "data_type": "COLLECTIVE_PROCEDURE", "siren": "891675829", "name": "FLEXIFAI (891675829)",
"id": "5e70eb10-…", "object": { "…": "…" }, "updates": [ { "after": { "…": "…" }, "before": { "…": "…" } } ] }
]
}
One event, several companies
{
"alert": "annonces-legales", "part": 1, "parts": 1,
"events": [
{ "kind": "add", "data_type": "LEGAL_NOTICE", "siren": "911031409", "name": "LA COMPAGNIE DES INSECTES INDUSTRIE (911031409)", "id": "…", "object": { "…": "…" }, "updates": [] },
{ "kind": "add", "data_type": "LEGAL_NOTICE", "siren": "900276320", "name": "RFENCE (900276320)", "id": "…", "object": { "…": "…" }, "updates": [] },
{ "kind": "add", "data_type": "LEGAL_NOTICE", "siren": "884598038", "name": "ETSEME (884598038)", "id": "…", "object": { "…": "…" }, "updates": [] }
]
}
Several events, several companies
{
"alert": "annonces-legales", "part": 1, "parts": 1,
"events": [
{ "kind": "add", "data_type": "LEGAL_NOTICE", "siren": "847597143", "name": "MEDETIA (847597143)", "id": "…", "object": { "…": "…" }, "updates": [] },
{ "kind": "add", "data_type": "LEGAL_NOTICE", "siren": "480405687", "name": "SMARTLINE SYSTEMS (480405687)", "id": "…", "object": { "…": "…" }, "updates": [] },
{ "kind": "add", "data_type": "LEGAL_NOTICE", "siren": "480405687", "name": "SMARTLINE SYSTEMS (480405687)", "id": "…", "object": { "…": "…" }, "updates": [] },
{ "kind": "add", "data_type": "LEGAL_NOTICE", "siren": "480405687", "name": "SMARTLINE SYSTEMS (480405687)", "id": "…", "object": { "…": "…" }, "updates": [] },
{ "kind": "add", "data_type": "LEGAL_NOTICE", "siren": "480405687", "name": "SMARTLINE SYSTEMS (480405687)", "id": "…", "object": { "…": "…" }, "updates": [] }
]
}