Send events to webhooks
Register an HTTPS endpoint that Elastio calls when an alert fires, check that deliveries succeed, and stand up a small receiver that authenticates and stores incoming events.
Before you begin
Section titled “Before you begin”- Use a token with the
integrationscategory:readto list webhooks and deliveries,writeto create, update or delete them. See Automate personal access tokens. - A webhook on its own sends nothing. Events reach it when an alert lists the webhook in its
channels.webhooks. See Configure alerts. - Webhook names must be unique per tenant.
Create a webhook
Section titled “Create a webhook”Give the webhook a name and an endpoint URL, and choose how Elastio authenticates to your endpoint with authentication.type:
type |
Fields you send |
|---|---|
none |
Nothing else |
basic_auth |
username, password |
bearer_token |
token |
api_key |
key (the key name, for example X-API-Key), value |
custom_headers |
custom_headers, an object of header name → value |
curl -X POST "https://acme.app.elastio.com/gateway/integration/v1/webhooks" \ -H "Authorization: Bearer $ELASTIO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "SOAR intake", "description": "Forwards Elastio alerts to the SOAR platform", "endpoint": "https://hooks.example.com/elastio", "authentication": { "type": "custom_headers", "custom_headers": { "X-Hook-Secret": "'"$HOOK_SECRET"'" } } }'import os, requests
BASE = "https://acme.app.elastio.com"HEADERS = {"Authorization": f"Bearer {os.environ['ELASTIO_TOKEN']}"}
body = { "name": "SOAR intake", "description": "Forwards Elastio alerts to the SOAR platform", "endpoint": "https://hooks.example.com/elastio", "authentication": { "type": "custom_headers", "custom_headers": {"X-Hook-Secret": os.environ["HOOK_SECRET"]}, },}resp = requests.post(f"{BASE}/gateway/integration/v1/webhooks", headers=HEADERS, json=body, timeout=30)resp.raise_for_status()webhook_id = resp.json()["data"]["id"]$Base = "https://acme.app.elastio.com"$Headers = @{ Authorization = "Bearer $env:ELASTIO_TOKEN" }$Body = @{ name = "SOAR intake" description = "Forwards Elastio alerts to the SOAR platform" endpoint = "https://hooks.example.com/elastio" authentication = @{ type = "custom_headers" custom_headers = @{ "X-Hook-Secret" = $env:HOOK_SECRET } }} | ConvertTo-Json -Depth 5$Created = Invoke-RestMethod -Method Post -Uri "$Base/gateway/integration/v1/webhooks" ` -Headers $Headers -ContentType "application/json" -Body $Body$Created.data.idThe response is 201, with the webhook wrapped in data. Sensitive values (password, token, value) are write-only and never returned:
{ "status": 201, "data": { "id": "660e8400-e29b-41d4-a716-446655440000", "name": "SOAR intake", "description": "Forwards Elastio alerts to the SOAR platform", "endpoint": "https://hooks.example.com/elastio", "status": "active", "authentication": { "type": "custom_headers" }, "created_at": "2026-10-12T09:30:00Z" }}List, pause and update webhooks
Section titled “List, pause and update webhooks”The list uses cursor pagination: pageSize (1–100, default 50) and pageToken from the previous response’s pagination.next_page_token. Each webhook includes used_in_alert_rules, success_rate and last_triggered_at.
curl "https://acme.app.elastio.com/gateway/integration/v1/webhooks?pageSize=50" \ -H "Authorization: Bearer $ELASTIO_TOKEN"webhooks, token = [], Nonewhile True: params = {"pageSize": 100, **({"pageToken": token} if token else {})} resp = requests.get(f"{BASE}/gateway/integration/v1/webhooks", headers=HEADERS, params=params, timeout=30) resp.raise_for_status() page = resp.json() webhooks += page["webhooks"] token = page["pagination"].get("next_page_token") if not token: break
for w in webhooks: print(f"{w['name']:<25} {w['status']:<8} rules={w.get('used_in_alert_rules')} success={w.get('success_rate')}%")(Invoke-RestMethod -Uri "$Base/gateway/integration/v1/webhooks?pageSize=50" -Headers $Headers).webhooks | Format-Table name, status, used_in_alert_rules, success_rate, last_triggered_at{ "webhooks": [ { "id": "660e8400-e29b-41d4-a716-446655440000", "name": "SOAR intake", "endpoint": "https://hooks.example.com/elastio", "status": "active", "authentication": { "type": "custom_headers" }, "used_in_alert_rules": 3, "success_rate": 98.5, "last_triggered_at": "2026-10-12T10:28:00Z", "created_at": "2026-10-12T09:30:00Z", "updated_at": "2026-10-12T09:30:00Z" } ], "pagination": { "next_page_token": null, "prev_page_token": null, "total_count": 1 }}PUT is a partial update: send only the fields to change. Set status to inactive to pause a webhook, or send a new authentication object to rotate its secret.
curl -X PUT "https://acme.app.elastio.com/gateway/integration/v1/webhooks/660e8400-e29b-41d4-a716-446655440000" \ -H "Authorization: Bearer $ELASTIO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "authentication": { "type": "custom_headers", "custom_headers": { "X-Hook-Secret": "'"$NEW_HOOK_SECRET"'" } } }'webhook_id = "660e8400-e29b-41d4-a716-446655440000"resp = requests.put( f"{BASE}/gateway/integration/v1/webhooks/{webhook_id}", headers=HEADERS, json={"status": "inactive"}, # pause deliveries timeout=30,)resp.raise_for_status()print(resp.json()["status"]) # "inactive"$WebhookId = "660e8400-e29b-41d4-a716-446655440000"Invoke-RestMethod -Method Put -Uri "$Base/gateway/integration/v1/webhooks/$WebhookId" -Headers $Headers ` -ContentType "application/json" -Body (@{ status = "inactive" } | ConvertTo-Json)Track deliveries
Section titled “Track deliveries”Check whether events are reaching your endpoint. The per-webhook deliveries list accepts period (for example 24h, 7d, 30d; default 7d), status (all, delivered, failed, in_progress, scheduled, retryable), and the same pageSize/pageToken pagination. Each delivery includes the event payload that was sent and a reason when it failed.
/gateway/integration/v1/webhooks/{webhook_id}/deliveriesGet delivery history for a specific webhookcurl "https://acme.app.elastio.com/gateway/integration/v1/webhooks/660e8400-e29b-41d4-a716-446655440000/deliveries?period=24h&status=failed" \ -H "Authorization: Bearer $ELASTIO_TOKEN"resp = requests.get( f"{BASE}/gateway/integration/v1/webhooks/{webhook_id}/deliveries", headers=HEADERS, params={"period": "24h", "status": "failed", "pageSize": 100}, timeout=30,)resp.raise_for_status()for d in resp.json()["deliveries"]: print(d["created_at"], d["event_type"], d["status"], d.get("reason"))$Uri = "$Base/gateway/integration/v1/webhooks/$WebhookId/deliveries?period=24h&status=failed"(Invoke-RestMethod -Uri $Uri -Headers $Headers).deliveries | Format-Table created_at, event_type, status, reason{ "deliveries": [ { "id": "1f2e3d4c-5b6a-4798-8a9b-0c1d2e3f4a5b", "webhook_id": "660e8400-e29b-41d4-a716-446655440000", "webhook_name": "SOAR intake", "event_type": "…", "status": "failed", "event": { … }, "reason": "…", "created_at": "2026-10-12T10:28:00Z", "updated_at": "2026-10-12T10:28:05Z" } ], "pagination": { "next_page_token": null, "prev_page_token": null, "total_count": 1 }}Related views:
GET /gateway/integration/v1/webhooks/deliveries: deliveries across all webhooks, filterable bynameas well asperiodandstatus.GET /gateway/integration/v1/webhooks/{webhook_id}/invocations: invocation history includinghttp_status_code,response_time_ms,request_payloadandresponse_body, useful for debugging your endpoint’s replies.GET /gateway/integration/v1/webhooks/statistics: tenant-wide totals (total_webhooks,active_webhooks,successful_deliveries,failed_deliveries, …) for aperiod.
Receive webhooks
Section titled “Receive webhooks”Your receiver needs to do three things: check the credential you configured in authentication, read the JSON body, and reply with a 2xx quickly. Do heavy processing asynchronously.
What the spec does and doesn’t define:
- Authentication. Elastio authenticates to your endpoint with the credential you set on the webhook (basic auth, bearer token, API key or custom headers). The spec doesn’t define a payload signature or signing secret, so this credential plus HTTPS is the verification mechanism.
- Payload. The event body is a JSON object, but the spec doesn’t publish its field-level schema. Each delivery record exposes the exact body sent as
event(andrequest_payloadon invocations), alongside theevent_type. Inspect real deliveries before you depend on specific fields, and parse defensively.
The minimal receivers below check the X-Hook-Secret custom header from the example above, store each body as one JSON line, and reply 204. Put them behind a TLS-terminating proxy or load balancer so Elastio reaches them over HTTPS.
# Simulate a delivery against your local receivercurl -i -X POST "http://localhost:8080/elastio" \ -H "Content-Type: application/json" \ -H "X-Hook-Secret: $HOOK_SECRET" \ -d '{"example": true}'# Expect: HTTP/1.0 204 No Content (401 with a wrong or missing secret)# receiver.py - standard library only (Python 3.9+)import hmac, json, osfrom http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
SECRET = os.environ["HOOK_SECRET"].encode()MAX_BODY = 5 * 1024 * 1024
class Handler(BaseHTTPRequestHandler): def do_POST(self): supplied = self.headers.get("X-Hook-Secret", "").encode() if not hmac.compare_digest(supplied, SECRET): self.send_response(401); self.end_headers(); return
length = int(self.headers.get("Content-Length", 0)) if length <= 0 or length > MAX_BODY: self.send_response(400); self.end_headers(); return try: event = json.loads(self.rfile.read(length)) except json.JSONDecodeError: self.send_response(400); self.end_headers(); return
with open("elastio-events.jsonl", "a") as f: # hand off to a queue in production f.write(json.dumps(event) + "\n") self.send_response(204); self.end_headers()
def log_message(self, fmt, *args): pass # keep secrets and payloads out of access logs
ThreadingHTTPServer(("0.0.0.0", 8080), Handler).serve_forever()# receiver.ps1 - System.Net.HttpListener, no extra modules$Listener = [System.Net.HttpListener]::new()$Listener.Prefixes.Add("http://+:8080/elastio/")$Listener.Start()while ($Listener.IsListening) { $Ctx = $Listener.GetContext() $Req = $Ctx.Request; $Res = $Ctx.Response if ($Req.HttpMethod -ne "POST" -or $Req.Headers["X-Hook-Secret"] -ne $env:HOOK_SECRET) { $Res.StatusCode = 401 } else { $Body = [System.IO.StreamReader]::new($Req.InputStream, $Req.ContentEncoding).ReadToEnd() try { $null = $Body | ConvertFrom-Json # validate JSON Add-Content -Path "elastio-events.jsonl" -Value ($Body -replace "`r?`n", "") $Res.StatusCode = 204 } catch { $Res.StatusCode = 400 } } $Res.Close()}- Run the receiver and expose it at the HTTPS URL you registered as
endpoint. - Attach the webhook to an alert’s
channels.webhooks(Configure alerts). - After the alert fires, compare the stored body with the delivery’s
eventfield from the deliveries list to confirm nothing was lost.
All endpoints
Section titled “All endpoints”Webhooks
11 operations in the reference →- GET
/gateway/integration/v1/webhooksList all webhooks for a tenant - POST
/gateway/integration/v1/webhooksCreate a new webhook - GET
/gateway/integration/v1/webhooks/statisticsGet webhook delivery statistics - GET
/gateway/integration/v1/webhooks/invocationsGet webhook invocation history (all webhooks) - GET
/gateway/integration/v1/webhooks/deliveries/filter-optionsGet filter options for webhook deliveries list - GET
/gateway/integration/v1/webhooks/deliveriesGet webhook delivery history (all webhooks) - GET
/gateway/integration/v1/webhooks/{webhook_id}Get webhook by ID - PUT
/gateway/integration/v1/webhooks/{webhook_id}Update a webhook - DELETE
/gateway/integration/v1/webhooks/{webhook_id}Delete a webhook - GET
/gateway/integration/v1/webhooks/{webhook_id}/invocationsGet invocation history for a specific webhook - GET
/gateway/integration/v1/webhooks/{webhook_id}/deliveriesGet delivery history for a specific webhook