MT4 & MT5 trade alerts to Discord, Telegram or Slack, without an EA
You want to know when a trade opens, when a stop loss hits, or when the terminal loses its broker connection. Not by opening MetaTrader, but as a message in a Discord channel your trading group shares, a Slack channel for your team, or a Telegram chat on your phone. MetaTrader posts to none of them by itself. The usual answer is an Expert Advisor that sits in your terminal and sends the messages.
This guide covers what MetaTrader already does on its own, how the EA route works and what it takes to keep it running, and the alternative: trade events sent as webhooks from a terminal you don't run, then passed on to Discord, Telegram, Slack or a spreadsheet through n8n, Make, Zapier or a few lines of code.
The short answer
MetaTrader 5 can push trade notifications to the MetaTrader app on your phone, but not to Discord, Telegram or Slack. For those you either run a notifier EA in a terminal that never goes offline, or let a hosted service watch the account and send each trade event as a webhook.
FxSocket does the second: it POSTs a signed JSON event to your HTTPS URL within seconds of a trade, on MT4 and MT5 alike, and a workflow tool or a small script turns it into a chat message.
What MetaTrader does on its own
If a notification on your own phone is all you need, MetaTrader 5 already has it. Under Tools, Options, Notifications you enter the MetaQuotes ID from the MetaTrader mobile app and pick one of two sources, as the platform help describes:
- Notifications from the local terminal. The desktop terminal reports every successful trade operation, balance operations and margin calls, for as long as it is running.
- Notifications from the trade server. The broker's server sends them, so a take profit that triggers while your computer is off still reaches your phone.
That is free and worth turning on. It also ends there. The messages go to the MetaTrader app and nowhere else: no Discord channel, no team, no spreadsheet, nothing another program can act on.
The usual way: a notifier EA
Everything beyond the mobile app goes through an Expert Advisor. The EA notices a trade and calls the chat service's API:
- Noticing the trade. On MT5 the EA handles
OnTradeTransaction, which fires for every deal on the account. MT4 has no such event, so MT4 EAs compare the open orders and the history on every tick or timer. - Sending it. MQL's WebRequest() posts to the Discord webhook, the Telegram Bot API or the Slack webhook. Every domain has to be added by hand under Tools, Options, Expert Advisors, "Allow WebRequest for listed URL". The call blocks the EA until the server answers, and indicators are not allowed to make it at all.
You can write the EA yourself in an afternoon, or buy one: the MQL5 Market lists dozens of Discord, Telegram and Slack notifiers for MT4 and MT5, free and paid. Either way the EA lives in your terminal, and that is the catch:
- It only sends while that terminal runs and is logged in. A laptop that sleeps means no alerts, so in practice you rent a VPS.
- Each account needs its own terminal with the EA attached, and MT4 and MT5 need their own builds.
- Trades that happen while the terminal is down, or while it restarts after an update, are usually never reported.
- Your bot token or webhook URL sits in the EA's inputs on that machine.
Trade events as webhooks, from a terminal you don't run
FxSocket runs a dedicated MT4 or MT5 terminal for each account you connect, in its cloud, and keeps it logged in. That is what its API talks to, and it is also what outbound webhooks watch. You register an HTTPS URL in the dashboard, choose the events and the accounts, and FxSocket sends one signed JSON request per event:
| Event | Sent when |
|---|---|
position.opened | A new position was opened, by you, an EA, a copier or the API. |
position.closed | A position was closed in full, including by stop loss, take profit or stop out. |
position.partially_closed | Part of a position was closed. |
position.modified | Stop loss or take profit of an open position moved. |
order.placed, order.triggered | A pending order was placed, or filled and became a position. |
balance.changed | A trade moved the balance, with the new balance and equity. |
terminal.disconnected | The terminal lost its broker connection for three minutes in a row. |
The full list has a few more, such as netting reversals and expired orders. Because the terminal is logged in to the account, not attached to one strategy, it sees every deal: from the mobile app, another terminal, an EA, a trade copier, or a stop loss that triggers on the broker's server. If FxSocket's connection to the terminal drops, it replays the deals it missed from the terminal's history, so an open or a close is not lost.
A closed position arrives like this (shortened):
{
"id": "evt_01jc8x4m2q9t7v3b5n6r0w2y4z",
"type": "position.closed",
"version": 1,
"createdAt": "2026-11-04T10:15:02.412Z",
"account": {
"id": "7f3a1c2e-0b4d-4e5f-9a6b-1c2d3e4fa8b9",
"platform": "mt5",
"login": 5012345,
"server": "TegasFX-Demo",
"name": "Main demo"
},
"data": {
"symbol": "EURUSD",
"positionType": "Buy",
"volume": 0.10,
"price": 1.08421,
"profit": 12.40,
"commission": -0.70,
"swap": 0.0,
"positionVolume": 0.0,
"timeUtc": "2026-11-04T10:15:02.301Z",
...
}
}positionType is the direction of the position, the same on MT4 and MT5 (on MT5, the deal that closes a Buy is a Sell deal; you don't have to care). The docs list every field.
Why you can't paste a Discord webhook URL
Discord, Slack and Telegram each accept their own message format. A Discord webhook wants {"content": "..."}, a Slack incoming webhook {"text": "..."}, and Telegram's Bot API a chat_id and a text posted to sendMessage. FxSocket sends the same event to every endpoint, so pointed straight at Discord, the request is refused, FxSocket keeps retrying, and after 72 hours without a single success it turns the endpoint off. Something has to sit in between and turn the event into a message. There are two easy ways.
Route 1: n8n, Make or Zapier, without code
In n8n it takes four steps:
- Create a workflow with a Webhook trigger, method POST, and copy its production URL. (The test URL only listens while the editor waits for a test event.)
- In the FxSocket dashboard, open Integrations, Outbound webhooks, add an endpoint with that URL, and pick the events, for example
position.openedandposition.closed. - Activate the workflow and press Send test in FxSocket. The execution shows up in n8n, with the event under
body. - Add a Switch node on
{{ $json.body.type }}and, per branch, a Discord, Telegram or Slack node with a message such asClosed {{ $json.body.data.symbol }}, profit {{ $json.body.data.profit }}.
Make works the same way with a Custom webhook trigger, and Zapier with the Catch Hook trigger of Webhooks by Zapier, which needs a paid Zapier plan. From there, the same flow can just as well append a row to Google Sheets for every closed trade. (For a trade journal in Notion there is a ready-made integration that needs no webhook.)
One thing these tools don't do out of the box is check FxSocket's signature. The webhook URL they give you is long and random, so treat it like a password: anyone who has it can post fake events into your channel. That is fine for notifications. For anything that acts on the events, verify the signature, as in route 2.
Route 2: a small Worker that checks the signature
If you would rather own the code, a serverless function is enough. This Cloudflare Worker runs on the free plan, verifies that each request really comes from FxSocket, and posts a one-line message to Discord:
// Cloudflare Worker: FxSocket trade events to a Discord channel.
// Secrets: FXSOCKET_SECRET (the endpoint's whsec_...) and DISCORD_WEBHOOK_URL.
export default {
async fetch(request, env) {
if (request.method !== "POST") return new Response(null, { status: 405 });
const body = await request.text(); // raw body: the signature covers it
if (!(await verify(request.headers, body, env.FXSOCKET_SECRET))) {
return new Response("invalid signature", { status: 401 });
}
const text = message(JSON.parse(body));
if (!text) return new Response(null, { status: 204 }); // an event we skip
const discord = await fetch(env.DISCORD_WEBHOOK_URL, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ content: text }),
});
// Anything but 2xx makes FxSocket try again later.
return new Response(null, { status: discord.ok ? 204 : 502 });
},
};
function message({ type, account, data }) {
const who = account
? account.name || `${account.platform.toUpperCase()} ${account.login}`
: "FxSocket";
switch (type) {
case "position.opened":
return `${who}: opened ${data.positionType} ${data.volume} ${data.symbol} at ${data.price}`;
case "position.closed":
return `${who}: closed ${data.positionType} ${data.volume} ${data.symbol} at ${data.price}, profit ${data.profit}`;
case "terminal.disconnected":
return `${who}: terminal disconnected (${data.reason})`;
case "webhook.test":
return `${who}: test event received`;
default:
return null;
}
}
async function verify(headers, body, secret) {
const id = headers.get("webhook-id");
const ts = headers.get("webhook-timestamp");
const age = Math.abs(Date.now() / 1000 - Number(ts));
if (!id || !ts || !(age <= 300)) return false; // also refuses NaN
try {
const key = await crypto.subtle.importKey(
"raw", base64(secret.replace(/^whsec_/, "")),
{ name: "HMAC", hash: "SHA-256" }, false, ["verify"],
);
const signed = new TextEncoder().encode(`${id}.${ts}.${body}`);
// During a secret rotation the header carries two signatures.
for (const entry of (headers.get("webhook-signature") ?? "").split(" ")) {
const [version, sig] = entry.split(",");
if (version === "v1" && sig &&
(await crypto.subtle.verify("HMAC", key, base64(sig), signed))) {
return true;
}
}
} catch {
// malformed base64: not from FxSocket
}
return false;
}
const base64 = (s) => Uint8Array.from(atob(s), (c) => c.charCodeAt(0));Store the endpoint's signing secret and the Discord webhook URL as secrets (npx wrangler secret put FXSOCKET_SECRET), deploy, and use the Worker's workers.dev URL as the FxSocket endpoint. For Telegram, create a bot with BotFather and replace the Discord request with a call to sendMessage:
const telegram = await fetch(
`https://api.telegram.org/bot${env.TELEGRAM_BOT_TOKEN}/sendMessage`,
{
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ chat_id: env.TELEGRAM_CHAT_ID, text }),
},
);
return new Response(null, { status: telegram.ok ? 204 : 502 });For Slack, post JSON.stringify({ text }) to your incoming webhook URL instead. The docs have the same check in Node.js and Python, and since FxSocket signs the Standard Webhooks way, their libraries for other languages work too.
Before you rely on it
- Retries. Any 2xx answer within 10 seconds counts as delivered. Everything else is retried, eight attempts over about 20 hours. An endpoint that fails for 72 hours is turned off and you get an email.
- Duplicates. Delivery is at least once: after a timeout your server did not notice, an event can arrive twice with the same
webhook-id. A doubled chat message is harmless; a spreadsheet or journal should skip ids it has seen. - Order. A retried event can arrive after a newer one. Sort by
data.timeUtc, not by arrival. - Gaps. Opens, closes and balance changes are replayed after a dropped connection, for up to 24 hours. Stop loss and take profit changes and pending-order events from that window are not.
- The log. The dashboard keeps every delivery and your endpoint's answer for 14 days, with a button to send one again.
Notifier EA or webhooks
| Notifier EA | FxSocket webhooks | |
|---|---|---|
| Where it runs | In your terminal, on your PC or a VPS | In FxSocket's cloud |
| When your computer is off | Nothing is sent | Events are still sent |
| MT4 and MT5 | Separate builds, often sold separately | Same events and payload for both |
| Trades from your phone or another terminal | Depends on the EA | Yes, every deal on the account |
| After a restart or a dropped connection | Usually lost | Missed deals are replayed from history |
| Destinations | The ones the EA supports | Any HTTPS URL: n8n, Make, Zapier, your code |
| Cost | Free to paid, plus a VPS | Included in FxSocket's €12 per account |
What you give up
- It costs money. MT5's own push notifications are free, and so are some notifier EAs if you already pay for a VPS. FxSocket is €12 per account per month; webhooks come with it at no extra charge.
- One more step. Chat apps need the translation in between, from route 1 or 2.
- Endpoint rules. The URL must be HTTPS on port 443 at a public address with an IPv4 record. A self-hosted n8n behind your home router needs a public HTTPS address first.
- Not for every setup yet. Accounts on a dedicated server are not supported yet, and webhooks carry trade, order and connection events, not prices. For price alerts, use MetaTrader's own alerts or FxSocket's WebSocket stream.
- You hand over a login. The terminal logs in with the credentials you give it. FxSocket is not a broker and cannot move money, but it can trade, so keep your API key private.
Trade alerts without a terminal to babysit
Connect any MT4 or MT5 account with its normal login, add your endpoint, and press Send test. Every open, close and stop loss from then on arrives as a signed webhook, wherever the trade came from.
Frequently asked questions
Can MetaTrader 5 send notifications to Discord?
Not by itself. MT5's built-in notifications go to the MetaTrader mobile app through your MetaQuotes ID. To reach Discord you either run an Expert Advisor that calls a Discord webhook with WebRequest(), in a terminal that stays online, or use a service that sends your trade events as webhooks and turn them into Discord messages with n8n, Make, Zapier or a few lines of code.
How do I get MT4 trade alerts on Telegram?
Either attach a Telegram notifier EA to an MT4 terminal that stays logged in, with api.telegram.org allowed under WebRequest, or connect the account to a hosted service that sends trade events as webhooks and forward them to your bot's sendMessage method with a workflow tool or a short script.
Does MetaTrader have webhooks?
Not for outgoing trade events. An Expert Advisor can call a URL with WebRequest(), so "MetaTrader webhooks" usually means an EA doing that, or the opposite direction: TradingView alerts sent as webhooks to a service that places the trade. FxSocket's outbound webhooks send events from the account to a URL you choose.
Will I get alerts for trades I place on my phone?
With FxSocket, yes. The terminal in FxSocket's cloud is logged in to the account and sees every deal, whether it came from the mobile app, another terminal, an EA, a trade copier or a stop loss on the broker's server. With a notifier EA it depends on how the EA watches the account.
Do I need a VPS for MetaTrader trade alerts?
For a notifier EA, in practice yes, because the terminal has to stay online. For webhooks, no: the terminal runs in FxSocket's cloud, and the receiver can be n8n Cloud, Make, Zapier or a serverless function.
Can I log closed trades to Google Sheets or Notion?
Yes. n8n, Make and Zapier all have Google Sheets steps, so a position.closed event can append a row. For Notion, FxSocket has a ready-made trade journal integration that needs no webhook at all.