This the documentation I used to build bubblehook, a Fizzy webhook receiver that sends updates to Slack. I hope this is useful. The post is written by AI, with a lot of rough notes fed to it from my reading the code, and observing the webhooks. Enjoy!
Fizzy doesn’t currently publish full webhook documentation, so I’ve put together a complete reference for anyone building integrations, automations, or notifications around Fizzy boards. This guide covers every event Fizzy emits, how the webhook delivery works, how signatures are generated, and what the payloads look like in JSON, Slack, Campfire, and Basecamp formats.
If you’re implementing a webhook receiver for Fizzy – this is the spec.
Supported Webhook Actions
Webhooks can subscribe to a specific set of event types. Fizzy sends a separate request for each event that occurs on the board the webhook belongs to.
Supported actions:
-
card_published— card created or published (drafts only fire when published) -
card_assigned/card_unassigned— a user added to or removed from a card -
card_closed/card_reopened— moved to or from “Done” -
card_postponed/card_auto_postponed— moved to “Not Now”, manually or by entropy -
card_triaged— placed into a column after triage -
card_sent_back_to_triage— returned to “Maybe?” -
card_board_changed— moved to a different board -
comment_created— non-system user posts a comment
Only events from the webhook’s own board are delivered. If a webhook becomes inactive, Fizzy skips it.
Request Envelope & Delivery Behavior
Every webhook delivery is a POST request with predictable headers and a signing mechanism.
HTTP Method
POST to your configured webhook URL.
Headers
-
User-Agent: fizzy/1.0.0 Webhook -
Content-Type: varies by endpoint type (JSON / Slack / Campfire / HTML) -
X-Webhook-Signature: hex HMAC-SHA256 of the raw request body -
X-Webhook-Timestamp: UTC timestamp when the event was created
Security & Timeouts
- DNS timeout: 2 seconds
- Connect/read timeout: 7 seconds
- Requests to private/loopback/link-local/broadcast addresses are blocked
Delivery Success Criteria
A delivery counts as successful only when your endpoint returns a 2xx HTTP status.
Failures are logged. If a webhook accumulates 10 consecutive failures within one hour, Fizzy automatically deactivates it to prevent endless retries.
Verifying Signatures
Fizzy signs the exact raw request body using the webhook’s signing_secret.
Example verification in Ruby:
payload = request.raw_post
timestamp = request.headers["X-Webhook-Timestamp"]
signature = request.headers["X-Webhook-Signature"]
expected = OpenSSL::HMAC.hexdigest("SHA256", signing_secret, payload)
ActiveSupport::SecurityUtils.secure_compare(signature, expected)
Payload Formats
Fizzy supports four output formats, depending on the webhook URL pattern.
1) Generic JSON (Default)
This is used for any non-Slack, non-Campfire/Basecamp URL.
Content-Type: application/json
Example (card_published):
{
"id": "evt_123",
"action": "card_published",
"created_at": "2024-05-01T15:04:05Z",
"eventable": {
"id": "card_456",
"title": "Logo refresh",
"status": "published",
"image_url": null,
"golden": false,
"last_active_at": "2024-05-01T15:04:05Z",
"created_at": "2024-04-29T10:00:00Z",
"url": "https://fizzy.localhost:3006/1234567/cards/42",
"board": {
"id": "brd_1",
"name": "Brand",
"all_access": true,
"created_at": "2024-04-01T12:00:00Z",
"creator": {
"id": "usr_1",
"name": "David",
"role": "owner",
"active": true,
"email_address": "[email protected]",
"created_at": "2024-03-01T12:00:00Z",
"url": "https://fizzy.localhost:3006/1234567/users/usr_1"
}
},
"column": null,
"creator": {
"id": "usr_1",
"name": "David",
"role": "owner",
"active": true,
"email_address": "[email protected]",
"created_at": "2024-03-01T12:00:00Z",
"url": "https://fizzy.localhost:3006/1234567/users/usr_1"
}
},
"board": { "...same as above..." },
"creator": { "...event initiator..." }
}
Card Fields
Match Fizzy’s internal card serializer:
-
id,title,status(draftedorpublished) -
image_url(nullable) -
goldenboolean flag -
last_active_at,created_at -
url -
board(nested summary) -
column(ornull) -
creator(user summary)
Comment Fields
When action: "comment_created", the eventable object contains:
-
id - timestamps
-
body.plain_textandbody.html -
creator -
reactions_url -
url
Notes
- Specific “particulars” (e.g., which user was assigned/unassigned) are not included — clients should re-fetch the card for detailed context.
- URLs include the account slug to support multi-tenancy.
- The column information is added only when there is something happening which is based on column (like changing the column).
2) Slack-Compatible Format
Triggered when the webhook URL matches:
https://hooks.slack.com/services/...
Content-Type: application/json
Body:
{ "text": "<mrkdwn text>" }
Conversion rules:
- HTML → Slack mrkdwn
-
<a href="url">text</a>→<url|text> -
<b>→*bold* -
<i>→_italic_ - All other HTML stripped
Example output:
David added Logo refresh ↗︎ https://fizzy.localhost:3006/1234567/cards/42
3) Basecamp Campfire (Chat Line)
Recognized when the URL matches:
/\d+/integrations/[^/]+/buckets/\d+/chats/\d+/lines
Content-Type: application/x-www-form-urlencoded
Body:
content=<html_snippet>
The snippet is the same HTML described below, URL-encoded.
4) Campfire-Compatible HTML
Matched when the URL resembles:
/rooms/\d+/\d+-[^/]+/messages
Content-Type: text/html
Body: raw HTML snippet.
HTML Template (Shared by Slack / Basecamp / Campfire)
Rendered from app/views/webhooks/event.html.erb:
<plain sentence about the event>
<a href="https://...">↗︎</a>
Rules:
-
Sentences come from
Event::Description - Personalized when Fizzy knows the acting user (“You added…”)
- Comments use “{creator} commented on {card title}”
- Link points to the card or comment URL
Delivery Flow Overview
Inside Fizzy:
-
Cards and comments emit events through the
Eventableconcern -
Event::WebhookDispatchJobidentifies relevant active webhooks -
Each webhook delivery is enqueued as a
Webhook::Delivery -
Webhook::DeliveryJobsends the request, records metadata, and handles success/failure - Excessive failures deactivate the webhook automatically
This keeps webhook delivery reliable and auditable without blocking the main application.
Final Notes
This post should serve as a complete, implementable reference until official documentation appears. If you’re building integrations – Slack apps, internal automations, reporting scripts, or custom activity feeds, this covers everything Fizzy emits today.