Channel webhooks

Everything that happens on your channels arrives as signed HTTP events. Register an endpoint once; consume idempotently.

Endpoints

bash
curl -s -X POST https://api.agentsky.dev/v1/channels/webhooks \
  -H "Authorization: Bearer $AST_TOKEN" -H 'content-type: application/json' \
  -d '{"url": "https://your.app/hook", "events": ["message.received", "channel.connected"]}'
# -> returns the whsec_ signing secret ONCE — store it

events defaults to ["message.received"]. GET /v1/channels/webhooks lists endpoints; DELETE /v1/channels/webhooks/{id} removes one; PATCH /v1/channels/webhooks/{id} re-enables a failure-disabled endpoint (keeping its secret) or re-picks events. The URL must be https (http is allowed for localhost during development).

The developer console manages the same endpoints — the signing secret is shown exactly once, at creation:

The developer console's webhook endpoints page showing the one-time whsec_ signing secret

Delivery format

Every delivery is a POST with a JSON body {id, type, createdAt, data} and these headers:

HeaderMeaning
X-Asteroids-Eventthe event type
X-Asteroids-Deliveryunique delivery id — your dedupe key
X-Asteroids-Timestampunix seconds, signed
X-Asteroids-Signaturev1=<hex> — HMAC-SHA256 over {timestamp}.{rawBody} with your whsec_ secret
js
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(secret, timestamp, rawBody, signatureHeader) {
  const expected = "v1=" + createHmac("sha256", secret).update(timestamp + "." + rawBody).digest("hex");
  return timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected));
}

Failed deliveries retry at 0s/5s/30s. After ~20 consecutive failures the endpoint is disabled and events stop until you re-create it. Retries reuse the delivery id — consume idempotently by X-Asteroids-Delivery.

Event catalog

EventWhen
message.receivedan inbound message on a webhook-bound conversation — carries connection_id, thread_id, message_id, text, author, and your connection metadata
message.reactionan end user reacted to a message
interaction.actiona button/select was tapped (platform spinners are acknowledged automatically)
command.receiveda slash command arrived on a connected surface
channel.connecteda connect link was claimed — carries your metadata
channel.needs_reauththe platform surface needs re-authorization
channel.disconnectedthe surface was disconnected
channel.unmapped_conversationa message arrived on a connected surface with no active binding — bind or reply via the threads API
turn.refuseda session-bound turn was refused (for example, out of credits)
delivery.faileda proactive delivery could not be posted

Local development: the SSE mirror

bash
curl -N https://api.agentsky.dev/v1/channels/events/stream \
  -H "Authorization: Bearer $AST_TOKEN"

GET /v1/channels/events/stream mirrors your webhook events as server-sent events — same payloads, no public URL or tunnel needed while developing. The developer console's Events tab renders the same stream live:

The developer console's Events tab streaming channel events live