Overview
Webhooks let Capture push raw telemetry data to your own HTTP endpoint as it arrives on the platform — either per data point (real-time) or grouped (batch). You configure everything through the REST API: create a webhook configuration once, link it to one or more devices, and add scopes to control exactly which data is forwarded.
Webhooks are an experimental feature. The API surface and payload format may still change between releases, and the feature may not be enabled on every environment. Contact the Capture team before building production integrations on top of it.
How it works
Data starts flowing to your endpoint once both of the following exist:
- A webhook configuration — the destination: URL, HTTP method, delivery mode, authentication, and headers. Owned by your company and reusable across devices.
- A device link with at least one enabled scope (data route) — which device forwards data, and how much of it (whole device, one connection, or one measurement).
Deleting a configuration or removing the last scope stops delivery automatically. Configuration changes propagate without any action on the device itself.
Webhook configuration (company-owned, reusable)
└─ linked device(s)
└─ scope(s): Device | Connection | Measurement
Headers
Every endpoint lives under https://portal.captureplatform.com and shares the headers below.
| Header | Value |
|---|---|
| AuthVersion | V0.0.1 (only value accepted; others return 401) |
| Authorization | [Bearer / ApiToken] <Received auth token> |
| companyView | (Optional) restricts the call to a specific company view |
See Authentication for how to obtain a token.
The caller needs the Device Overview module right on the owning company: View for the GET endpoints, Manage for everything that creates, updates, links, or deletes. Missing rights return 403.
Endpoints at a glance
| Method | Path | Purpose |
|---|---|---|
POST | /v2/webhooks?companyId={companyId} | Create a webhook configuration |
GET | /v2/webhooks?companyId={companyId} | List all webhook configurations of a company |
GET | /v2/webhooks/{id} | Get a single webhook configuration |
PUT | /v2/webhooks/{id} | Update a webhook configuration |
DELETE | /v2/webhooks/{id} | Delete a webhook configuration |
POST | /v2/webhooks/{id}/devices/{deviceId} | Link a device to a configuration |
GET | /v2/webhooks/{id}/devices | List devices linked to a configuration |
DELETE | /v2/webhooks/{id}/devices/{deviceId} | Unlink a device |
POST | /v2/webhooks/{id}/devices/{deviceId}/routes | Add a scope (data route) to a device link |
GET | /v2/webhooks/{id}/devices/{deviceId}/routes | List scopes on a device link |
PUT | /v2/webhooks/{id}/devices/{deviceId}/routes/{routeId} | Edit a scope |
DELETE | /v2/webhooks/{id}/devices/{deviceId}/routes/{routeId} | Remove a scope |
Webhook configurations
Create a configuration
POST /v2/webhooks?companyId={companyId}
{
"name": "My integration endpoint",
"url": "https://example.com/capture-ingest",
"method": "POST",
"deliveryMode": "Realtime",
"authType": "Bearer",
"authPayload": "{\"token\": \"my-secret-token\"}",
"customHeaders": "{\"X-Environment\": \"production\"}",
"includeMetadataHeaders": true,
"includeRetryHeader": true
}
| Field | Type | Notes |
|---|---|---|
name | string | Display name. Required, max 255 characters. |
url | string | The endpoint Capture will call. Required, max 2048 characters. Use HTTPS. |
method | string | POST (default), PUT, or PATCH. |
deliveryMode | string | Realtime (one request per data point) or Batch (multiple points per request, batched per measurement). |
authType | string | None, Bearer, ApiKey, Basic, or Custom. |
authPayload | string | (Optional) JSON string with the credentials for authType — see below. Stored encrypted, never returned. |
customHeaders | string | (Optional) JSON string of static header key/value pairs added to every request (non-secret context, e.g. an environment tag). |
includeMetadataHeaders | bool | Adds User-Agent and X-Webhook-Timestamp headers to deliveries (default: true). |
includeRetryHeader | bool | Adds an X-Webhook-Retry-Count header to deliveries (default: true). |
The authPayload per auth type
authPayload is a JSON object serialized as a string. Its expected keys depend on authType:
authType | authPayload | What your endpoint receives |
|---|---|---|
None | omit | No authentication header. |
Bearer | {"token": "<token>"} | Authorization: Bearer <token> |
ApiKey | {"apiKeyValue": "<key>"} | X-API-Key: <key> |
Basic | {"username": "<user>", "password": "<pass>"} | Authorization: Basic <base64(user:pass)> |
Custom | {"headers": {"X-My-Header": "value", ...}} | Each entry as its own request header. |
authPayload is encrypted at rest and never returned by any GET endpoint — responses only expose authType. On update (PUT), leave authPayload out (null) to keep the stored credentials, send a new value to replace them, or send an empty string ("") to clear them.
Response
Create, get, and update return the configuration without credentials:
{
"id": "3f1c2b4e-…",
"companyId": 42,
"name": "My integration endpoint",
"url": "https://example.com/capture-ingest",
"method": "POST",
"deliveryMode": "Realtime",
"authType": "Bearer",
"customHeaders": "{\"X-Environment\": \"production\"}",
"includeMetadataHeaders": true,
"includeRetryHeader": true
}
GET /v2/webhooks?companyId={companyId} returns a list of the same shape. DELETE /v2/webhooks/{id} returns true and immediately stops delivery for all devices linked to that configuration.
Linking devices
POST /v2/webhooks/{id}/devices/{deviceId} — link a device (no body).
GET /v2/webhooks/{id}/devices — list linked devices.
DELETE /v2/webhooks/{id}/devices/{deviceId} — unlink a device.
{
"deviceId": 1234,
"webhookConfigId": "3f1c2b4e-…",
"createdAt": "2026-07-09T08:00:00Z"
}
A configuration can be linked to many devices, and a device can be linked to many configurations. Linking the same device twice returns 409. The device and the configuration must belong to the same company.
A link alone does not forward any data yet — add at least one enabled scope (below) to start delivery for that device.
Scopes (data routes)
Scopes control which portion of a linked device's data is forwarded. A link can carry multiple scopes; a data point is delivered when any enabled scope matches it.
Add a scope
POST /v2/webhooks/{id}/devices/{deviceId}/routes
{
"scopeLevel": "Connection",
"connectionTag": "PLC-1",
"measurement": null,
"enabled": true
}
scopeLevel | connectionTag | measurement | Forwards… |
|---|---|---|---|
Device | must be empty | must be empty | Everything the device sends. |
Connection | required | must be empty | Everything on one connection. |
Measurement | required | required | One measurement on one connection. |
Violating these rules returns 400; an identical scope on the same link returns 409. connectionTag and measurement are limited to 255 characters.
The response (and each item of the GET …/routes list) is the scope with its generated id:
{
"id": "9a7d5e21-…",
"scopeLevel": "Connection",
"connectionTag": "PLC-1",
"measurement": null,
"enabled": true
}
Use PUT …/routes/{routeId} with the same body shape to edit a scope (e.g. toggle enabled), and DELETE …/routes/{routeId} to remove it. Routing activates and deactivates automatically — no device restart or manual sync is needed.
What your endpoint receives
Deliveries use the configured HTTP method with Content-Type: application/json.
Request headers
| Header | When | Value |
|---|---|---|
User-Agent | includeMetadataHeaders: true | Capture.Cloud.Stream.WebhookHttpClient/<version> |
X-Webhook-Timestamp | includeMetadataHeaders: true | ISO 8601 timestamp of when the delivery was sent. |
X-Webhook-Retry-Count | includeRetryHeader: true | 0 on the first attempt, incremented per retry. |
| (custom headers) | customHeaders configured | Each configured key/value pair. |
| (auth header) | authType ≠ None | See authPayload per auth type. |
Real-time payload
One request per data point:
{
"Timestamp": "2026-07-09T12:00:00.000Z",
"Metadata": {
"Company": "your-company",
"GatewayId": "source-gateway-id",
"Measurement": "sensor_reading"
},
"Point": {
"Measurement": "sensor_reading",
"Fields": { "temperature": 22.5, "humidity": 45.0 },
"Tags": { "location": "room-1" },
"UtcTimestamp": "2026-07-09T11:59:58.000Z"
}
}
Batch payload
Same envelope, but points are grouped per measurement into a Points array:
{
"Timestamp": "2026-07-09T12:00:00.000Z",
"Metadata": {
"Company": "your-company",
"GatewayId": "source-gateway-id",
"Measurement": "sensor_reading"
},
"Points": [
{
"Measurement": "sensor_reading",
"Fields": { "temperature": 22.5 },
"Tags": { "location": "room-1" },
"UtcTimestamp": "2026-07-09T11:59:58.000Z"
},
{
"Measurement": "sensor_reading",
"Fields": { "temperature": 22.6 },
"Tags": { "location": "room-1" },
"UtcTimestamp": "2026-07-09T12:00:08.000Z"
}
]
}
Responses and retries
Return a 2xx status to acknowledge a delivery. Other responses are handled as follows:
| Your response | Behavior |
|---|---|
2xx | Delivered — done. |
4xx (except 429) | Dropped immediately — treated as a permanent misconfiguration. |
429 | Retried with exponential backoff. |
5xx / timeout | Retried with exponential backoff. |
Endpoints should respond quickly (connect/request timeouts are in the order of 10s/30s). Do heavy processing asynchronously after acknowledging.
Error responses
| Status | When |
|---|---|
400 | Validation failed (missing name/url, invalid enum value, scope rule violation, …). |
401 | AuthVersion is not V0.0.1, or the token is missing/invalid/expired. |
403 | Valid token but missing Device Overview → View/Manage on the owning company. |
404 | Company, configuration, device, link, or route not found (or not within your company). |
409 | Device already linked, or an identical scope already exists on the link. |
Example: from zero to data on your endpoint
BASE="https://portal.captureplatform.com"
AUTH=(-H "AuthVersion: V0.0.1" -H "Authorization: Bearer <token>")
# 1. Create the webhook configuration
WEBHOOK_ID=$(curl -s "$BASE/v2/webhooks?companyId=42" "${AUTH[@]}" \
-H "Content-Type: application/json" \
-d '{
"name": "My integration endpoint",
"url": "https://example.com/capture-ingest",
"method": "POST",
"deliveryMode": "Realtime",
"authType": "Bearer",
"authPayload": "{\"token\": \"my-secret-token\"}"
}' | jq -r '.id')
# 2. Link a device
curl -s -X POST "$BASE/v2/webhooks/$WEBHOOK_ID/devices/1234" "${AUTH[@]}"
# 3. Add a scope — forward everything the device sends
curl -s "$BASE/v2/webhooks/$WEBHOOK_ID/devices/1234/routes" "${AUTH[@]}" \
-H "Content-Type: application/json" \
-d '{ "scopeLevel": "Device", "enabled": true }'
# Data now flows to https://example.com/capture-ingest