Docs
Webhooks
Inbound channel webhooks (Telegram, Discord, Slack) and billing webhooks (Stripe, LemonSqueezy, Polar). HMAC verification.
Webhooks
Hangar receives webhooks for inbound channel events and billing provider events. Every webhook is HMAC-verified at the edge before reaching the handler.
Channel webhooks
| Channel | URL | Verification |
|---|---|---|
| Telegram | /api/webhooks/telegram |
Telegram X-Telegram-Bot-Api-Secret-Token matches TELEGRAM_WEBHOOK_SECRET_TOKEN. |
| Discord | /api/webhooks/discord |
Ed25519 signature using DISCORD_PUBLIC_KEY (per Discord interaction spec). |
| Slack | /api/webhooks/slack |
Slack v0 signature using SLACK_SIGNING_SECRET. |
If verification fails, we return 401 and emit an audit-log entry.
Replays older than 5 minutes are rejected even with a valid signature.
Billing webhooks
| Provider | URL |
|---|---|
| Stripe | /api/webhooks/stripe |
| LemonSqueezy | /api/webhooks/lemonsqueezy |
| Polar | /api/webhooks/polar |
Each provider uses its native signing scheme. Secrets come from
STRIPE_WEBHOOK_SECRET, LEMONSQUEEZY_WEBHOOK_SECRET, and
POLAR_WEBHOOK_SECRET respectively.
Outbound webhooks
Hangar does not currently send outbound webhooks to your origin. State is exposed via:
- The MCP server (
/api/mcp) for read + control. - The realtime SSE stream (
/api/realtime) for live activity. - The audit log table for retroactive queries.
If you need outbound delivery (e.g. an SQS-shaped fan-out), open a discussion and describe the use case.
Inbound rules
- Inbound webhooks return
200even when the message is dropped (e.g. filtered by persona); we use the JSON body to encode the action so the channel does not retry. - We accept
Content-Type: application/jsonand the channel-native form encodings (Slack URL-encoded, etc.). - Bodies are size-limited to 1 MB; larger payloads return
413.