API reference

Webhook Events

What your endpoint receives: Tasks' outgoing webhooks, signed with the webhook's secret, and Mirage's events for apps, signed with the app's key.

Tasks

A POST with a JSON body for each event a webhook subscribes to, narrowed by its spaces and query. A delivery is tried up to three times; a 4xx other than 429 isn't retried.

Headers

  • x-issues-event

    The event, such as issue.created.

  • x-issues-delivery

    The delivery's id, the same on each retry, so a delivery you've had can be skipped.

  • x-issues-signature

    t=<unix seconds>,v1=<hex>: an HMAC-SHA256 of <t>.<raw body> keyed with the webhook's secret.

Check the signature

import { createHmac, timingSafeEqual } from "node:crypto"

// body: the raw request body as it arrived; header: x-issues-signature
export function verify(body, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")))
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false
  const expected = createHmac("sha256", secret).update(`${parts.t}.${body}`).digest("hex")
  return expected.length === parts.v1?.length && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
}

Body

{
  id: string
  event: "issue.created" | "issue.updated" | "issue.transitioned" | "issue.deleted" | "issue.moved" | "issue.sla_breached" | "comment.created" | "comment.updated" | "comment.deleted" | "worklog.created" | "attachment.created" | "link.created" | "link.deleted" | "sprint.started" | "sprint.completed" | "version.released"
  at: string
  actor: {
    type: "user" | "key" | "system" | "automation"
    id: string
    label?: string
  }
  issue: null | {
    id: string
    key: string
    summary: string
    type?: string
    status?: string
    statusCategory: "done" | "todo" | "inprogress"
    priority?: string
    resolution?: string
    assignee: null | {
      id: string
      email?: string
      name?: string
    }
    reporter: null | {
      id: string
      email?: string
      name?: string
    }
    labels: string[]
    dueDate?: string
    storyPoints?: number
    fields: {
      [key: string]: boolean | null | string | number | string[] | {
        parent: string
        child?: string
      }
    }
    created: string
    updated: string
  }
  changes?: {
    field: string
    from?: unknown
    to?: unknown
    fromText?: string
    toText?: string
  }[]
  transition?: {
    id: string
    name: string
    from: string
    to: string
  }
  commentId?: string
  sprintId?: string
  versionId?: string
  from?: string
}

Mirage apps

Events an app turns on are POSTed to its interactions endpoint as type 0, signed like interactions: x-signature-ed25519 over x-signature-timestamp and the body, with the app's public key.

Mirage apps and bots

Body

{
  id: string
  applicationId: string
  type: 0
  version: 1
  timestamp: string
  event:
    | { type: "message_mention"; serverId: string; channelId: string; message: Message }
    | { type: "member_join"; serverId: string; member: { userId: string; name: string; joinedAt: number } }
}
  • message_mention

    A message mentions the app's bot or replies to one of its messages, in a channel the bot can read. Messages from bots never send it.

  • member_join

    Someone joined a server the app's bot is in.