FreqTrade publishes messages to your ntfy server via webhooks. FreqDroid subscribes to the same topics and displays native OS notifications. Works on Android and iOS.
← Back to overview · Add the Python callback →
FreqTrade bot → POST → ntfy server → stream → FreqDroid → notification
You can use the public ntfy.sh server for testing, but for production use with trading data a self-hosted instance is strongly recommended.
docker run -p 80:80 -v /var/cache/ntfy:/var/cache/ntfy binwiederhier/ntfy serve
services:
ntfy:
image: binwiederhier/ntfy
command: serve
ports:
- "80:80"
volumes:
- ntfy-cache:/var/cache/ntfy
volumes:
ntfy-cache:
Put this behind a reverse proxy (nginx, Caddy, Traefik) with TLS so the URL is https://ntfy.example.com. HTTPS is required for notifications to work reliably on Android 9+ and iOS.
See the ntfy self-hosting docs for full configuration options.
FreqDroid derives a topic name for each bot automatically:
topic = "freqdroid-" + botName.lowercase()
.replace(' ', '-')
.filter { alphanumeric or '-' }
| Bot name in app | ntfy topic |
|---|---|
MyBot | freqdroid-mybot |
Binance BTC | freqdroid-binance-btc |
bot_1 | freqdroid-bot1 |
The Settings screen shows the computed topic as a hint.
In your FreqTrade config.json, add a webhook section that POSTs to your ntfy topic. Use "format": "raw" so FreqTrade sends plain text. See the full event list and template tokens on the overview page.
"webhook": {
"enabled": true,
"url": "https://ntfy.example.com/freqdroid-mybot",
"format": "raw",
"retries": 3,
"retry_delay": 0.2,
"allow_custom_messages": true,
"entry": {
"data": "Entry placed (#{trade_id})\n{direction} {pair} @ {open_rate:.6f} | stake {stake_amount:.2f} {stake_currency}"
},
"entry_fill": {
"data": "Trade opened (#{trade_id})\n{pair} @ {open_rate:.6f} | stake {stake_amount:.2f} {stake_currency}"
},
"entry_cancel": {
"data": "Entry cancelled (#{trade_id})\n{pair} entry order cancelled"
},
"exit": {
"data": "Exit placed (#{trade_id})\n{pair} @ {limit:.6f}"
},
"exit_fill": {
"data": "Trade closed (#{trade_id})\n{pair} | profit {profit_amount:.2f} {stake_currency} ({profit_ratio:.2%})"
},
"exit_cancel": {
"data": "Exit cancelled (#{trade_id})\n{pair} exit order cancelled"
},
"protection_trigger": {
"data": "Pair locked: {pair}\n{reason}\nLocked until {lock_end_time}"
},
"protection_trigger_global": {
"data": "Protection triggered: {reason}\nAll pairs locked until {lock_end_time}"
},
"strategy_msg": {
"data": "{msg}"
},
"status": {
"data": "Status: {status}"
},
"startup": {
"data": "{status}"
},
"warning": {
"data": "Warning: {status}"
},
"exception": {
"data": "Exception: {status}"
}
}
If you wire up the Python callback, drop
entry_fillandexit_fillfrom this block — the callback emits richer messages viadp.send_msg(), and keeping the built-in fills would give you double notifications.
Restart FreqTrade after editing config.json. Test with:
curl -d "Test message (#1)" https://ntfy.example.com/freqdroid-mybot
https://ntfy.example.com) — no trailing slash, no topic name.iOS uses BGAppRefreshTask to poll ntfy in the background at the configured interval. iOS controls exact timing — the interval is a minimum, not a guarantee. The app also polls when it comes to the foreground.
No notifications arriving
POST https://ntfy.example.com/freqdroid-<botname>Notifications stop after a while (Live mode)
The app maintains a long-lived HTTP streaming connection. If the server closes the connection, the app automatically reconnects with exponential back-off (2 s up to 60 s). No action needed.
ntfy requires authentication
Enter your credentials in the Username and Password fields below the server URL. The app sends a Basic auth header when both fields are non-empty. ntfy access tokens are not supported — use a dedicated ntfy user with a password.
Tapping a notification doesn’t open the trade
The notification text must contain #<trade_id> (e.g. (#123)). Make sure your webhook templates include #{trade_id} — see the example above.