Pass-through integration for local government · records are never stored System status1-559-251-7767Sentra familySign in
SentraBridgeIntegration platform
DevelopersWebhooks

Webhooks, in and out

Send an event to a flow the moment something happens in your system. Receive signed messages from SentraBridge about platform status and your activity log. Both ways are signed with HMAC-SHA256.

§ How it works

Sending an event into a flow

The message is never queued or stored: it is used and dropped.

  1. 01Your system callsIt POSTs JSON to the flow's private address.
  2. 02SentraBridge checksSignature, calling address and that the flow is on.
  3. 03The flow runsInside the request. The message is then dropped.
  4. 04You get the resultA run ID and counts in the answer.

The address and the call

Give a flow the start step When a system calls in. Its private address looks like https://app.sentrabridge.com/hooks/… and is shown on the step. Treat it like a password.

POST a JSON object. The step says where the list of records is inside it, and which field is the record ID. The flow must be turned on.

The answer arrives when the flow has finished, with the run ID and counts. The limit is 120 calls a minute per calling address.

Send a signed event
BODY='{"id":"REQ-1001","status":"open"}'
SIGNATURE=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SIGNING_SECRET" | sed 's/^.* //')

curl -s -X POST "$SENTRABRIDGE_URL/hooks/$PRIVATE_ADDRESS" \
  -H "Content-Type: application/json" \
  -H "X-Signature: sha256=$SIGNATURE" \
  -d "$BODY"
Answer (200)
{
  "run_id": "01m769bjym26wdm4ej2rw2k7wn",
  "status": "ok",
  "records": 1,
  "sent": 0
}

Signature checks a flow can use

Each flow chooses how it checks the sender, on its start step. The signing secret is saved encrypted and never shown again.

Sign the exact bytes you send. Schemes with a timestamp are refused when it is more than 5 minutes off, which stops replays. Keep your server clock in sync.

A flow can also list the only addresses allowed to call it. Refused calls are written to the activity log.

Signature schemes
Choice on the stepHeaderHow it is checked
Don't check (the private address is the only secret)NoneNo check. The private address is the only secret.
Another system: HMAC-SHA256 of the body in a headerX-Signature (or the header you name)HMAC-SHA256 of the raw body with the shared secret, as hex or base64, with or without a "sha256=" prefix.
Another system: a shared token in a headerX-Webhook-Token (or the header you name)The shared secret itself, compared in constant time.
Sentra 311 or Sentra Scheduler (X-Civic311-Signature)X-Civic311-Signature + X-Civic311-Timestamp"sha256=" + hex HMAC-SHA256 of "{timestamp}.{body}". Refused when the timestamp is more than 5 minutes off.
SentraQueue (X-SentraQueue-Signature)X-SentraQueue-Signature"t={unix},v1={hex}" where v1 is the HMAC-SHA256 of "{t}.{body}". Refused when t is more than 5 minutes off.
CivicCA (X-CivicCA-Signature)X-CivicCA-Signature"t={unix},v1={hex}" where v1 is the HMAC-SHA256 of "{t}.{body}". Refused when t is more than 5 minutes off.
SentraDeed (X-SentraDeed-Signature)X-SentraDeed-Signature"t={unix},v1={hex}" where v1 is the HMAC-SHA256 of "{t}.{body}". Refused when t is more than 5 minutes off.
Sentra CMS (X-SentraCMS-Signature)X-SentraCMS-Signature"sha256=" + hex HMAC-SHA256 of the raw body.

Answers

Retry on 429 and 5xx. A 502 means the flow ran and failed; the run ID in the body leads to the reason in run history. Other codes won't succeed on retry.

The same call from Node.js
import crypto from 'node:crypto';

const body = JSON.stringify({ id: 'REQ-1001', status: 'open' });
const signature = crypto.createHmac('sha256', process.env.SIGNING_SECRET).update(body).digest('hex');

const res = await fetch(process.env.FLOW_ADDRESS, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'X-Signature': `sha256=${signature}` },
  body,
});
console.log(res.status, await res.json());
Status codes from a flow's address
CodeWhat it means
200The run that handled the message, with counts.
401The signature check failed.
403The calling address is not on the flow's list.
404Unknown address.
409The flow is turned off.
422The body was not JSON, or was empty.
429More than 120 calls in a minute. See the Retry-After header.
502The flow ran and failed. The body still carries the run ID.
§ How it works

Receiving SentraBridge's webhooks

Status updates for anyone; activity-log forwarding for workspace admins.

  1. 01Something happensAn incident is posted, or the activity log gets entries.
  2. 02SentraBridge signsHMAC-SHA256 over the timestamp and body.
  3. 03Your endpointChecks the signature and the timestamp.
  4. 04Answer 2xxWithin 10 seconds for status, 15 for log entries.

Verifying a signature

Every signed message has X-SentraBridge-Timestamp (Unix seconds) and X-SentraBridge-Signature. The signature is sha256= plus the hex HMAC-SHA256 of the timestamp, a dot and the raw body.

Compute it over the bytes you received, before parsing the JSON. Compare in constant time, and refuse timestamps more than five minutes old.

A known answer to test your code
InputValue
Secretwhsec_test
Timestamp1768496400
Body{"type":"subscription.verify"}
Signaturesha256=bc1904fb176ca5096ac52d6ca0b4ac0e50083adb8b26c9c2bf12656a8cfcdac4
Verify in Python
import hashlib, hmac, time

def verified(secret: str, headers, raw_body: bytes) -> bool:
    timestamp = headers.get('X-SentraBridge-Timestamp', '')
    sent = headers.get('X-SentraBridge-Signature', '')
    if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > 300:
        return False  # too old: possible replay
    expected = 'sha256=' + hmac.new(secret.encode(), timestamp.encode() + b'.' + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sent)
Verify in Node.js
import crypto from 'node:crypto';

export function verified(secret, headers, rawBody) {
  const timestamp = headers['x-sentrabridge-timestamp'] ?? '';
  const sent = headers['x-sentrabridge-signature'] ?? '';
  if (!/^\d+$/.test(timestamp) || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
  return expected.length === sent.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sent));
}

Status updates

Register a public https address. SentraBridge first sends a signed subscription.verify message; answer 2xx with the challenge in the body to prove the address is yours.

The answer to your registration has the signing secret, shown once. After that you get a POST for each of: incident.posted, incident.updated, maintenance.posted, maintenance.updated, maintenance.reminder. The type is also in X-SentraBridge-Event.

Answer within 10 seconds. After 20 failed deliveries the subscription pauses until you register again. To stop, POST to the unsubscribe_url in any message.

Register a status webhook
curl -s -X POST "$SENTRABRIDGE_URL/public/status/subscribe" \
  -H "Content-Type: application/json" \
  -d '{"channel": "webhook", "url": "https://ops.example.gov/hooks/sentrabridge-status", "components": ["api", "schedules"]}'
A status message
{
  "type": "incident.posted",
  "sent_at": "2026-01-15T17:00:00+00:00",
  "status_page": "https://sentrabridge.example/status/",
  "incident": {
    "id": "01jexampleincident0000000000",
    "title": "Scheduled runs are delayed",
    "kind": "incident",
    "impact": "degraded",
    "status": "investigating",
    "components": [
      "schedules"
    ],
    "starts_at": "2026-01-15T17:00:00+00:00",
    "ends_at": null,
    "resolved_at": null
  },
  "update": {
    "at": "2026-01-15T17:00:00+00:00",
    "status": "investigating",
    "text": "Scheduled flows are starting late. We are looking into it."
  },
  "unsubscribe_url": "https://app.sentrabridge.example/public/status/unsubscribe?token=EXAMPLE"
}

Activity log forwarding

Workspace admins can forward the activity log to a webhook under Settings → Records & audit (syslog and OpenTelemetry are there too). Entries arrive in batches, oldest first.

Headers: X-SentraBridge-Timestamp, X-SentraBridge-Sink, X-SentraBridge-Signature. The signature is sent when a signing secret is saved on the destination, so save one.

Each entry carries its tamper-check hashes (hash, prev_hash), so your log store can prove nothing was removed. Entries describe actions and settings, never record contents.

A batch of log entries
{
  "source": "sentrabridge",
  "sent_at": "2026-01-15T17:00:00+00:00",
  "events": [
    {
      "id": 4821,
      "chain": "01jexampleworkspace00000000",
      "workspace_id": null,
      "workspace": null,
      "at": "2026-01-15T17:00:00+00:00",
      "action": "flow.toggled",
      "summary": "Turned on Nightly permit export",
      "actor": {
        "id": null,
        "name": "System",
        "email": null
      },
      "subject": {
        "type": "flow",
        "id": "01jexampleflow0000000000000"
      },
      "ip": "203.0.113.10",
      "hash": "ab12ab12ab12ab12ab12ab12ab12ab12ab12ab12ab12ab12ab12ab12ab12ab12",
      "prev_hash": "cd34cd34cd34cd34cd34cd34cd34cd34cd34cd34cd34cd34cd34cd34cd34cd34",
      "meta": null
    }
  ]
}