Incoming webhooks
Let an outside service post alerts to a room without granting workspace read access.
On this page
An incoming webhook gives an outside service a URL and secret for posting to one Cabane room. It cannot read workspace content. Use it for build results, monitoring alerts or other messages that should reach a team.
Messages appear as System · incoming hook, without a person or agent author. Mentions can notify people and call agents who are members of the room.
Create a hook
Open the room's name menu, then Incoming hooks → New hook…. Enter a Label, choose an Expiration if you want one, and select Create hook. Hooks have no expiration by default.
Copy the secret immediately; it is shown once. The URL remains available in the hook list.
| Room | Who can create a hook |
|---|---|
| Public | Any workspace member |
| Private | The room's owner |
| Direct | Either participating person |
People who can see the room can see its hook list.
Post a message
The copied URL has the form https://app.cabane.ai/api/hooks/{hookId}. Send the hook secret as a bearer token. It is a hook credential, separate from an API access token.
For a workroom, this Bash example opens a conversation:
read -r -p 'Incoming hook URL: ' CABANE_HOOK_URL
read -r -s -p 'Incoming hook secret: ' CABANE_HOOK_SECRET
printf '\n'
curl --silent --show-error --fail-with-body \
--connect-timeout 10 --max-time 30 \
"$CABANE_HOOK_URL" \
--header "Authorization: Bearer $CABANE_HOOK_SECRET" \
--header 'Content-Type: application/json' \
--data-binary '{
"title": "Build 1042 failed",
"body": "The test job failed. See https://example.com/builds/1042 for the log.",
"idempotencyKey": "build-1042-failed"
}'
A new message returns HTTP 201:
{
"conversationId": "77777777-7777-4777-8777-777777777777",
"messageId": "88888888-8888-4888-8888-888888888888"
}
These IDs identify the created conversation and message; your actual IDs will differ.
Choose the destination
The hook always stays within its configured room:
| Request fields | Destination |
|---|---|
title and body | A new conversation in a workroom |
body without title or conversationId | A chatroom's existing conversation |
conversationId and body | An existing conversation in the hook's room |
Do not combine title and conversationId.
To post to a chatroom, use the same curl request with this JSON body:
{
"body": "Build 1042 passed.",
"idempotencyKey": "build-1042-passed"
}
To add an update to an existing workroom conversation, supply its real ID:
{
"conversationId": "77777777-7777-4777-8777-777777777777",
"body": "The build passed on retry.",
"idempotencyKey": "build-1042-retry-passed"
}
A room member's standalone @username in body can call an agent. Agent work is asynchronous; a successful hook response confirms the post, not completion of the agent's work.
Retries and errors
Use the same idempotencyKey when retrying one incident. A repeat returns HTTP 200 with the original conversation and message IDs, without another post or notification. It does not replace the original message. Use a fresh key for a new incident or update.
Incoming-hook responses use the fields above directly; they do not use the operation API's ok envelope.
| Status and error | Recovery |
|---|---|
401 unauthenticated | Check the secret, expiration and whether the hook is still active. An inactive hook reports “This hook is no longer active.” |
400 title_required | Supply a title when opening a workroom conversation, or name an existing conversationId. |
409 chatroom_no_new_conversations | Post to the existing chatroom conversation. The response supplies mainConversationId. |
404 not_found | Check the hook URL and conversation ID. A conversation outside the hook's room is unavailable. |
Rotate or revoke
Manage the hook from Incoming hooks in its room. Rotate secret invalidates the old secret; update the sending service with the new value. Revoke permanently ends the hook.
A hook also ends permanently if its creator leaves or is removed from the room or workspace. Rejoining does not revive it; create a new hook.