Skip to content

Developer docs

Incoming webhooks

Post into a Hubs channel from a script, a build server or any tool with a signed request.

An incoming webhook is a private address another tool posts to. Each post appears in one channel with the Webhook label and the hook's name.

ScopeWhereWhoLimit
HubManage, Features, Incoming webhooks (the hub must turn the feature on)Owners and admins10 per hub, one channel each
OrganizationOrganization admin, IntegrationsThe owner and managers20 per organization, any text channel of its hubs, with a name and an avatar color

Send a post

POST /api/webhooks/in/<hook id> with JSON { "content": "..." } (up to 2,000 characters) and two headers made with the hook's secret:

  • X-Hubs-Timestamp: Unix seconds, within five minutes of the server's clock.
  • X-Hubs-Signature: v1= and the hex HMAC-SHA256 of "<timestamp>.<raw body>".
import { createHmac } from "node:crypto";

// Returns the body and headers for one post. Send the body exactly as
// returned: the signature covers these bytes.
export function signHubsPost(secret, content, now = Date.now()) {
  const body = JSON.stringify({ content });
  const timestamp = String(Math.floor(now / 1000));
  const signature =
    "v1=" +
    createHmac("sha256", secret)
      .update(timestamp + "." + body)
      .digest("hex");
  return {
    body,
    headers: {
      "Content-Type": "application/json",
      "X-Hubs-Timestamp": timestamp,
      "X-Hubs-Signature": signature,
    },
  };
}

A post answers 201 with the new message's id.

Rules

  • Each signature is accepted once; sending the same signed request again answers 409.
  • Each hook takes up to 30 posts a minute, then answers 429.
  • An unknown hook, a hub hook whose hub turned the feature off, and an organization hook whose hub left the organization all answer 404.
  • The secret is shown once and can be replaced at any time; the old one stops at once.
  • The hub's content rules apply to every post.