Security & Permissions
A webhook URL is a posting credential. The module treats it that way.
The webhook is a secret
The token in https://stoat.chat/api/webhooks/{id}/{token} authorises anyone who holds it to post into that channel. Stoat’s own documentation compares it to a password.
- Store it only in the WHMCS provider settings.
- Do not paste it into tickets, screenshots, chat or source control.
- If it is exposed, delete the webhook in Stoat and create a new one, then update WHMCS.
- Use one webhook per destination channel so a leak has a limited blast radius.
Log redaction
Every request is recorded in the WHMCS module log. The URL is rewritten before it is written:
https://stoat.chat/api/webhooks/01ARZ3NDEKTSV4RRFFQ69G5FAV/[redacted]
The webhook ID is kept because it is not secret and helps identify which channel a failure belongs to. The token never appears in the log, in exception messages or in the documentation.
Transport security
| Control | Behaviour |
|---|---|
| Scheme | HTTPS only. Plain http:// URLs are rejected before any request. |
| TLS verification | Peer and host verification are always enabled. |
| Redirects | Not followed, so a redirect cannot move the token to another host. |
| Protocols | cURL is restricted to HTTPS. |
| Timeouts | Connection timeout 5 seconds; total request timeout 1–30 seconds, default 10. |
| Credentials in URL | Rejected. |
| Query string / fragment | Rejected. |
URL validation
Before a request is made, the webhook must satisfy all of these:
https://scheme.- A host of
stoat.chator a subdomain of it, unless Allow Self-Hosted Host is ticked. - A path ending in
/api/webhooks/{id}/{token}. - Port 443, unless self-hosted is allowed.
- No userinfo, query string or fragment.
- 2048 characters or fewer.
This blocks accidental plain-HTTP sends, token smuggling through redirects or userinfo, and arbitrary endpoints being reached through the provider setting. The host allowlist is deliberately opt-out rather than open by default.
Permissions
| Permission | Used for | If missing |
|---|---|---|
SendMessages | Every request | The notification fails; nothing can be delivered. |
SendEmbeds | Formats embed and both | Fails, or degrades to Markdown in both mode when strict delivery is off. |
Masquerade | Sender name or avatar | Fails, or degrades to the webhook’s identity when strict delivery is off. |
ManageRole | Not used | Masquerade colour is never set by this module. |
MentionEveryone | Not used | Mentions are neutralised, and no mention flags are sent. |
Mention safety
Notification text can contain client-supplied strings. @everyone, @here and @online are replaced with a zero-width-space variant before sending, so the words still read naturally but cannot ping the server.
Content safety
- Only
httpandhttpsURLs are turned into Markdown links; other schemes stay as text. - Link labels have Markdown control characters escaped.
- URLs are percent-encoded for spaces and parentheses so a crafted URL cannot break out of the link target.
- Attribute labels and values are flattened to one line, preventing injected layout or fake headings.
- The module never sets
mention_everyone,mention_onlineor role mentions.
Self-hosted Stoat
Self-hosted instances are supported by ticking Allow Self-Hosted Host. HTTPS is still mandatory, and the path must still end in /api/webhooks/{id}/{token}. Use a certificate the WHMCS server trusts; a self-signed certificate will fail TLS verification by design.
Rotation checklist
- Create a new webhook in the same channel.
- Update the provider webhook, or every rule override that used the old one.
- Save the provider settings and confirm the test message arrives.
- Delete the old webhook in Stoat.
- Check the module log for failures from any rule still pointing at the old URL.