Webhooks

Webhooks let PulseLABS push events to your server the moment something happens — a debate finishes, a simulation tick completes, or a report is ready. Instead of polling the API, you register an HTTPS endpoint and receive a signed HTTP POST within seconds of each event.

All webhook requests are signed with HMAC-SHA256. Always verify the X-PulseLabs-Signature header before processing the payload.

Events

The following events can be subscribed to per endpoint. You can subscribe to any combination when creating a webhook.

debate.synthesized
A conversation message triggers a full multi-agent debate and produces a synthesis.
data: { conversationId, synthesis }
simulation.tick.completed
One turn of a SimEngine simulation advances (one debate round per active scenario).
data: { simulationId, turn, stateCount }
simulation.report.ready
A simulation is finalized and the full report is ready for retrieval.
data: { simulationId, name, turnsCompleted }
webhook.test
Sent when you click "Send test" in the dashboard or call the test endpoint.
data: { webhookId, message, timestamp }

Payload structure

Every delivery is an HTTP POST with a JSON body. The envelope is the same for all events:

{
  "event": "debate.synthesized",
  "data": {
    "conversationId": "clx1a2b3c",
    "synthesis": { ... }
  },
  "timestamp": "2026-06-07T14:22:05.123Z",
  "webhookId": "wh_abc123"
}

Request headers

X-PulseLabs-EventEvent name, e.g. debate.synthesized
X-PulseLabs-Signaturesha256=<hmac-hex> — use this to verify the payload
X-PulseLabs-DeliveryUnique delivery attempt UUID
User-AgentPulseLabs-Webhooks/1.0
Content-Typeapplication/json

Retry behavior

If your endpoint returns a non-2xx status or times out (10 second limit), PulseLABS retries automatically:

  • Attempt 1 — immediately
  • Attempt 2 — 1 second later
  • Attempt 3 — 5 seconds later

After 3 failures the delivery is marked as failed and recorded in the delivery log. Respond with 200 as quickly as possible — process the event asynchronously to avoid timeouts.

Verifying signatures

Compute an HMAC-SHA256 over the raw request body using your webhook secret. Compare using a timing-safe equality check to prevent timing attacks.

Always parse the raw body bytes before JSON-decoding. Parsing first and re-serializing will produce a different byte sequence and fail the check.
import crypto from 'crypto'

function verifySignature(rawBody, signatureHeader, secret) {
  const expected = 'sha256=' +
    crypto.createHmac('sha256', secret)
          .update(rawBody)
          .digest('hex')
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader),
  )
}

// Express example
app.post('/hooks/pulselabs', express.raw({ type: '*/*' }), (req, res) => {
  const sig  = req.headers['x-pulselabs-signature']
  const body = req.body          // raw Buffer

  if (!verifySignature(body, sig, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Signature mismatch')
  }

  const event = JSON.parse(body.toString())
  console.log('Event:', event.event, event.data)
  res.status(200).send('ok')
})

API reference

Register a new webhook endpoint. The signing secret is returned once in this response and never again — store it immediately.

Request body
NameTypeRequiredDescription
urlstringrequiredHTTPS endpoint to receive events
eventsstring[]requiredArray of event names to subscribe to
descriptionstringoptionalHuman-readable label for the dashboard
Response
{
  "webhook": {
    "id": "wh_abc123",
    "url": "https://your-server.com/hooks/pulselabs",
    "events": ["debate.synthesized", "simulation.report.ready"],
    "isActive": true,
    "secret": "whsec_a1b2c3...",   // shown once, store securely
    "createdAt": "2026-06-07T14:00:00Z"
  }
}
curl -X POST http://localhost:3001/v1/webhooks \
  -H "x-api-key: sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/hooks/pulselabs",
    "events": ["debate.synthesized", "simulation.report.ready"],
    "description": "Production alerts"
  }'

List all registered webhook endpoints. Secrets are masked — only the last 4 characters are shown.

Response
{
  "webhooks": [
    {
      "id": "wh_abc123",
      "url": "https://your-server.com/hooks/pulselabs",
      "events": ["debate.synthesized"],
      "isActive": true,
      "secretPreview": "whsec_xxx...c3d4",
      "createdAt": "2026-06-07T14:00:00Z"
    }
  ]
}

Update a webhook endpoint's URL, subscribed events, description, or active state.

Path / Query parameters
NameTypeRequiredDescription
idstringrequiredWebhook ID
Request body
NameTypeRequiredDescription
urlstringoptionalNew endpoint URL
eventsstring[]optionalNew set of events to subscribe to
descriptionstringoptionalNew label
isActivebooleanoptionalPause (false) or resume (true) delivery
Response
{ "webhook": { "id": "wh_abc123", "isActive": false, ... } }

Permanently delete a webhook and all its delivery history.

Path / Query parameters
NameTypeRequiredDescription
idstringrequiredWebhook ID
Response
204 No Content

Send a webhook.test event to the registered URL right now. Useful to confirm connectivity and signature verification before going live.

Path / Query parameters
NameTypeRequiredDescription
idstringrequiredWebhook ID
Response
{
  "success": true,
  "statusCode": 200
}
curl -X POST http://localhost:3001/v1/webhooks/<id>/test \
  -H "x-api-key: sk_live_xxx"

Retrieve the last 50 delivery attempts for a webhook, ordered newest first. Use this to diagnose failures without leaving the API.

Path / Query parameters
NameTypeRequiredDescription
idstringrequiredWebhook ID
Response
{
  "deliveries": [
    {
      "id": "del_xyz",
      "event": "debate.synthesized",
      "statusCode": 200,
      "success": true,
      "retryCount": 0,
      "deliveredAt": "2026-06-07T14:22:05Z",
      "createdAt": "2026-06-07T14:22:04Z"
    },
    {
      "id": "del_abc",
      "event": "debate.synthesized",
      "statusCode": 503,
      "success": false,
      "retryCount": 3,
      "deliveredAt": null,
      "createdAt": "2026-06-07T13:10:00Z"
    }
  ]
}
curl http://localhost:3001/v1/webhooks/<id>/deliveries \
  -H "x-api-key: sk_live_xxx"