Skip to content

Webhooks

Signed deliveries, outbound only

Point an endpoint at the events you care about. Two kinds are offered: what happened in the business, and what agents were allowed, refused or asked a person about — the second being the half a security review comes for.

Signature
X-Xyranik-Signature
Algorithm
HMAC-SHA256
Tolerance
300s
Body fields
id, event, occurredAt, data

What happened in the business

  • contact.created
    A new contact joined a list
  • contact.unsubscribed
    Someone unsubscribed
  • campaign.sent
    An email campaign finished sending
  • post.published
    A social post went live
  • post.failed
    A platform rejected a post
  • order.created
    An order was recorded
  • order.attributed
    An order was traced back to a campaign
  • form.submitted
    Someone submitted a hosted form

What an agent was allowed to do

The trust layer emits too. These are how an external system watches what software was permitted to do in the workspace — including the refusals, which is the half that usually goes unrecorded.

  • agent.request.authorized
    An agent was allowed to do something
  • agent.request.denied
    An agent was refused
  • agent.request.approval_required
    An agent’s request is waiting for a person
  • agent.request.approved
    A person approved an agent’s request
  • agent.request.rejected
    A person turned down an agent’s request
  • agent.request.expired
    Nobody answered an agent’s request in time
  • agent.request.completed
    An authorised action was carried out
  • agent.paused
    An agent was paused, suspended or revoked
  • agents.stopped
    Every agent in the workspace was stopped
  • agents.resumed
    Agents were started again

What a delivery looks like

  • X-Xyranik-Event
    The event type, so you can route without parsing the body.
  • X-Xyranik-Delivery
    A unique id for this delivery attempt, for your own de-duplication.
  • X-Xyranik-Signature
    The signature, and the timestamp it was made with.
BodyExample
{
  "id": "evt_…",
  "event": "order.created",
  "occurredAt": "2026-09-18T09:15:00.000Z",
  "data": { … }
}

Check the signature before you trust it

The header is t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>", keyed with the endpoint’s secret>. Compare against the raw body — re-serialising parsed JSON changes the bytes and the signature will not match — and compare in constant time.

NodeExample
const [t, v1] = header.split(',').map(part => part.split('=')[1])
const expected = createHmac('sha256', secret)
  .update(t + '.' + rawBody)
  .digest('hex')

const ok =
  timingSafeEqual(Buffer.from(expected), Buffer.from(v1)) &&
  Math.abs(Date.now() / 1000 - Number(t)) < 300

The timestamp check is what stops a captured delivery being replayed at you later.

The signing secret is sealed at rest, and rotation signs with both the old and the new secret for an overlap — so you can move to the new one without dropping a delivery.

What Xyranik will not do

  • No private addresses
    A webhook is never delivered to a private, loopback or internal address. The address is checked when you save it and again before every send, because a hostname that resolved publicly once can resolve inward later.
  • Outbound only
    These are deliveries from Xyranik to you. Nothing about this endpoint lets anything reach inward.
  • No unannounced events
    Internal events describing how Xyranik decided are deliberately not subscribable yet: their shapes are still moving, and an external contract is a promise not to move them.

The reasoning behind all three is on the Webhooks platform page.