Webhooks¶
Webhooks connect the GRC-ITSM platform to external systems over HTTP. They run in two directions, and which direction you need decides which page you want.
-
Incoming
An external system posts into the platform. A scanner files a validation result, a SIEM raises an alert, a CI pipeline records evidence.
Start with Authentication.
-
Outgoing
A platform event posts out to an external system. A ticket is created, an approval completes, an SLA is about to breach.
See Outgoing Webhooks.
Incoming Webhooks¶
Incoming webhooks let a system file evidence directly, without knowing the platform's ticket types, category paths or field structure. You send flat JSON to a URL; the platform validates it and creates or updates the right records.
Three endpoints are available.
| Endpoint | What it files | On repeat calls |
|---|---|---|
| Send an Alert | A security or availability alert, as an Alert ticket | Updates the same ticket when you send an external_id |
| Upsert a Validation Check | The definition of a validation check, as a ticket under the KSI or Rule it evidences | Updates the same check ticket |
| Create a Validation Record | The result of running a check, as a row of run history | Adds a new run, or corrects one you name |
Checks and records are deliberately separate¶
A validation check is split across two endpoints because the two things change on different schedules.
Upsert a Validation Check says what a check is and how it is evidenced. Call it when your check catalogue changes, typically once per check, per system.
Create a Validation Record says what happened when the check ran. Call it every scan cycle.
A typical integration calls the first once to register the check and get a ticket id back, then calls the second on every run against that id.
Register once -> Upsert Validation Check -> returns ticket_id
Every run -> Create Validation Record -> ticket_id + result
Recording a run also updates the check ticket's status, so the KSI tree above it reflects the current evidence without anyone maintaining it by hand.
Rules that apply to every incoming call¶
- Send flat JSON. No envelope, no nesting. Every documented field is a top-level key.
- Omit what you do not have. Omitted optional fields keep their current value. Sending
""is treated the same as omitting, for text fields. - One record per call. These are single-item endpoints. To send many, loop.
- Keep each field under 4,000 characters. Values are read with a JSON extraction that returns nothing past that length, so an over-long value is dropped rather than truncated.
- Use UTC. Any timestamp you send is stored as-is and interpreted as UTC.
- Retries are safe when the call carries a stable identity. Because a
202does not confirm the write, a retry is often the right response to uncertainty. Sendexternal_idon an alert; validation checks are always keyed. For a validation record, send an explicitvalidation_datetime(your scan's own timestamp) or avalidation_key, otherwise a retry composes a new key from the current time and inserts a second run.
Identifying the system¶
Every endpoint that creates a ticket needs to know which system it belongs to. Send either:
| Field | Type | Notes |
|---|---|---|
site_id |
integer | The system's id. Preferred - unambiguous and survives renames. |
system_name |
string | Matched against the system's System Name. Used only when site_id is absent. Breaks on rename. |
Send site_id wherever you can. If neither resolves to a system, the call is rejected before anything is written.
The client, requester and SLA are all derived from the system. You never send them.
Responses¶
Every call returns 202 Accepted with an empty body. The endpoint acknowledges receipt and processes the payload asynchronously, so the response tells you the request arrived and nothing more. A rejected payload returns 202 exactly like an accepted one.
Two things follow, and both shape how you build an integration.
You cannot read success or failure from the response. Treat 202 as "delivered", not "done". Where the outcome matters, verify out of band:
- Open the runbook's Log tab under Configuration > Integrations > Custom Integrations > Integration Runbooks, which names the step that failed and the rejection reason.
- Or query back for what you expected to be written, using your own identifier:
external_idfor an alert,validation_idfor a check.
Anything other than 202 is a transport-level problem rather than a payload one. 401 means the endpoint rejected your credential (an expired token, a wrong secret, or a signature that did not match); 404 means the URL is wrong or the runbook does not expose a public endpoint.
You do not receive the ticket id. This matters most for validation work, because Create a Validation Record needs the check's ticket_id and the endpoint that creates the check cannot hand it back. Resolve it once, out of band, and store it against your check definition:
- Look the check up through the Halo API or the UI by its
validation_id, which is written to the ticket's Validation ID field, or by its summary<control_id> | <check_title>. - The id is stable. Re-posting the same
validation_idupdates that same ticket rather than creating another, so you resolve it once per check and reuse it on every run.
Design your caller around this
A pipeline that posts a check and immediately posts a run against an id it expected in the response will not work. Register checks as a setup step, resolve and store their ids once, then report runs against the stored ids.
Each endpoint documents its own numbered rejection reasons. Those numbers appear on the runbook's Log tab, not in any response you receive.
Where to go next¶
- Authentication: choose the authentication method the sending system can meet, and set up its credential. Every incoming call needs one.
- Then the endpoint you need: Alerts, Validation Checks, or Validation Records.
- For the other direction, Outgoing Webhooks.