# 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 `200` even 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/json` and the channel-native
form encodings (Slack URL-encoded, etc.).
- Bodies are size-limited to 1 MB; larger payloads return `413`.