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.
| Scope | Where | Who | Limit |
|---|---|---|---|
| Hub | Manage, Features, Incoming webhooks (the hub must turn the feature on) | Owners and admins | 10 per hub, one channel each |
| Organization | Organization admin, Integrations | The owner and managers | 20 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.