Gestionnaire externe
Le gestionnaire externe est un pont entre votre code et Telegram. Lorsque la logique du constructeur ne suffit pas, vous confiez une réaction distincte à votre serveur : il reçoit des événements et répond par des commandes au format Telegram Bot API, tandis que GetMyBot prend en charge le transport, la livraison et tout ce qui concerne Telegram.
Qu'est-ce que c'est
Dans GetMyBot, les réactions sont construites visuellement : déclencheur → conditions → actions. Une logique complexe ou non standard — votre propre base de données, des calculs, des appels à des systèmes tiers, du ML, des scénarios ramifiés — est difficile à exprimer dans le constructeur. Le gestionnaire externe lève ce plafond : vous connectez votre propre backend et lui confiez le traitement de réactions entières.
Le service externe reçoit de GetMyBot des événements (déclencheur activé, utilisateur a répondu par texte, a appuyé sur un bouton) et commande en retour au bot ce qu'il doit envoyer. GetMyBot exécute les commandes avec son propre token de bot — au-dessus fonctionnent la limitation de débit, les nouvelles tentatives, la déduplication et le journal de dialogue.
Modèle de fonctionnement
- GetMyBot est le serveur WebSocket. Votre service se connecte à lui de son propre chef et n'a pas besoin d'adresse publique ni d'endpoint webhook. GetMyBot ne fait pas de connexions sortantes vers votre service, il n'y a donc pas de surface d'attaque SSRF.
- Authentification par token d'intégration. Le service présente le token lors de la connexion ; GetMyBot le compare avec le hash et maintient la connexion.
- Événements et commandes au format Telegram Bot API. Le service reçoit un événement et répond par un tableau d'appels de type « méthode + paramètres », exactement comme lors du travail avec un bot ordinaire.
- Les commandes sont exécutées par GetMyBot avec son propre token. Le service ne communique jamais directement avec Telegram : les commandes passent par la validation et entrent dans l'outbox interne de GetMyBot, d'où elles sont envoyées avec le token du bot, avec limitation de débit, nouvelles tentatives, déduplication et journal.
Une seule connexion gère tous les dialogues d'une intégration — les événements de différents utilisateurs sont multiplexés et distingués par le champ session_id.
Configuration de l'intégration
- Ouvrez la section Intégrations dans le menu de gauche et cliquez sur Ajouter une intégration.
- Sélectionnez le service Gestionnaire externe et donnez-lui un nom.
- Après la création, la carte affiche :
- Adresse WebSocket — de la forme
wss://api.mybot.app/ext/ws/<integrationID>, où<integrationID>est l'UUID de cette intégration. C'est à cette adresse que votre service se connecte. Sans l'id dans le chemin, la connexion ne peut pas s'établir. - Token de connexion — affiché une seule fois lors de la création. Copiez-le et conservez-le en lieu sûr ; il est impossible de le consulter à nouveau (il n'est stocké que sous forme de hash).
- Renouvellement du token — ce bouton génère un nouveau token et invalide immédiatement l'ancien. Après le renouvellement, mettez à jour le token dans votre service.
- Indicateur en ligne — indique si votre service maintient une connexion active en ce moment.
- Adresse WebSocket — de la forme
Paramètres supplémentaires de l'intégration, influençant le protocole :
session_ttl_seconds— durée de vie de la session en secondes (par défaut3600).on_unavailable_reaction_id— réaction de secours lancée si le service est hors ligne au moment du déclenchement (voir Fiabilité).
Association à une réaction
Le gestionnaire externe ne se connecte pas via un déclencheur distinct, mais via une action. Dans l'éditeur de réaction, ajoutez l'action Transférer au gestionnaire externe et sélectionnez l'intégration souhaitée.
N'importe quel déclencheur GetMyBot existant peut ouvrir un dialogue — commande, texte, appui sur un bouton, paramètre depuis un lien, requête web entrante, planification. Lorsqu'un tel déclencheur s'active et atteint l'action, GetMyBot ouvre une session proxy et envoie au service l'événement session.open.
Si à ce moment le service n'est pas connecté, GetMyBot lancera la réaction de secours définie dans les paramètres de l'intégration (on_unavailable_reaction_id). Si aucune réaction de secours n'est définie, il ne se passera rien (silencieusement, sans erreur pour l'utilisateur).
Connexion et autorisation
Endpoint : GET /ext/ws/{integrationID} via WebSocket (wss). integrationID est l'UUID de l'intégration de type « Gestionnaire externe ». Limite de taille de trame : 256 KiB.
Il est possible de s'authentifier de deux façons.
1. En-tête lors de la négociation (méthode recommandée). Transmettez le token dans l'en-tête Authorization :
Authorization: Bearer <token>
2. Par trame d'authentification. Si l'en-tête Bearer n'est pas transmis, envoyez dans les 10 secondes suivant l'établissement de la connexion la première trame :
{ "type": "auth", "token": "<token>" }
Si la trame d'authentification n'est pas reçue dans les 10 secondes, la connexion est fermée avec le code 4401. Un token incorrect dans l'une ou l'autre méthode entraîne également une fermeture 4401.
Le token est généré lors de la création de l'intégration, affiché une seule fois et stocké uniquement sous forme de hash (sha256), la vérification étant à durée constante. Rotation du token :
POST /api/bots/{botID}/integrations/{integrationID}/rotate-token
L'endpoint renvoie le nouveau token en texte clair ; l'ancien cesse immédiatement de fonctionner.
Événements : bot → service
Chaque événement est une trame JSON (EventEnvelope). Le champ type est l'un des suivants : session.open, message, callback, session.cancel, session.expired.
Ensemble complet des champs de l'enveloppe :
{
"type": "session.open",
"session_id": "string",
"bot_id": "string",
"user": {
"tg_user_id": 123456789,
"first_name": "Ivan",
"username": "ivan",
"params": { "utm": "promo" }
},
"chat": { "id": 123456789, "type": "private" },
"update": { "...": "Telegram Update brut (raw JSON)" },
"event": { "...": "événement GetMyBot normalisé (raw JSON)" }
}
session_id— identifiant de la session (dialogue).bot_id— identifiant du bot.user— données de l'utilisateur :tg_user_id(int64),first_nameetusernameoptionnels, ainsi queparams— paramètres de l'utilisateur (y compris depuis le lien), map clé→valeur.chat— chat :id(int64) ettype(par exemple"private").update— Telegram Update brut (raw JSON). Présent poursession.open,message,callback.event— événement GetMyBot normalisé (raw JSON).
Le contenu varie selon les types :
session.open— le déclencheur s'est activé, un nouveau dialogue est ouvert. Contientupdate(Update brut) etevent(événement normalisé complet),user.paramsest renseigné.message— reçu lorsque le service « maintient une attente » (expect=text/any) et que l'utilisateur a envoyé un message. Le contenu est identique à celui desession.open.callback— l'utilisateur a appuyé sur un bouton précédemment envoyé par le service.updateest brut ; dansuser, seultg_user_idest renseigné ;eventcontient uniquement le callback_data original défini par le service dans le bouton :
{ "callback_data": "<valeur originale définie par le service dans le bouton>" }
session.cancel/session.expired— enveloppe minimale :session_id,bot_id,user.tg_user_id,chat.id(pourcancel, égalementchat.type). Les champsupdate/eventsont absents. Ces événements ne sont délivrés que si le service est en ligne — ils ne sont pas mis en file d'attente hors ligne.
Commandes : service → bot
Une commande est une trame JSON (CommandEnvelope). Le champ type est l'un des suivants : execute, session.close, auth, ping, pong.
{
"type": "execute",
"session_id": "string",
"methods": [
{ "method": "sendMessage", "params": { "text": "Bonjour" } }
],
"expect": "none"
}
session_id— session à laquelle la commande se rapporte.token— uniquement pourtype: "auth"(voir Connexion et autorisation).methods— tableau d'objets{ "method": "<nom de la méthode Telegram Bot API>", "params": { ... } }. Le nom de la méthode correspond exactement à celui de Telegram Bot API (par exemplesendMessage),paramsest l'objet des paramètres de cette méthode.expect— si le service attend une réponse de l'utilisateur :"none","text"ou"any"(une valeur vide est interprétée commenone).session.close— ferme explicitement le dialogue.
Les commandes ne vont pas directement vers Telegram : GetMyBot les valide et les place dans son outbox, d'où elles sont envoyées avec le token du bot (avec limitation de débit, nouvelles tentatives, déduplication et journal).
Traitement de execute
La commande execute est entièrement abandonnée si : session_id est vide ; la session est introuvable ou n'est pas en statut open ; la session appartient à une autre intégration ; la session a expiré.
Ensuite :
- Limitation de débit : jusqu'à 60
executepar minute par session (fenêtre glissante). Au-delà de la limite — abandon. - Jusqu'à 30 méthodes dans un seul
execute; les méthodes supplémentaires sont silencieusement abandonnées. - Chaque méthode est vérifiée par rapport à la liste blanche (voir Méthodes autorisées) — une méthode absente de la liste est rejetée.
- Protection de chat : si
paramscontientchat_idoufrom_chat_idavec une valeur différente de0et différente duchat_idde la session, la méthode entière est rejetée (il est interdit d'envoyer dans un chat étranger).chat_idpeut ne pas être spécifié du tout : GetMyBot y substituera de force le chat de la session. - Les boutons inline avec
callback_datasont automatiquement tokenisés (format internex:<token>) ; la durée de vie du bouton est valable jusqu'à l'expiration de la session. Les boutonsurl/webapp/switch_inlinene sont pas modifiés.
Méthodes autorisées
Le service commande le bot, donc l'ensemble des méthodes est limité à une liste blanche — uniquement l'envoi et la gestion des messages du dialogue en cours. Les méthodes absentes de la liste sont silencieusement ignorées (ce n'est pas une erreur).
- envoi :
sendMessage,sendPhoto,sendDocument,sendVideo,sendAudio,sendMediaGroup,sendAnimation,sendVoice,sendLocation,sendChatAction; - modification et suppression :
editMessageText,editMessageCaption,editMessageReplyMarkup,deleteMessage; - réponse à un appui :
answerCallbackQuery; - transfert et copie :
forwardMessage,copyMessage; - épinglage :
pinChatMessage,unpinChatMessage.
Les méthodes de niveau compte (setWebhook, getUpdates, logOut, close, setMyCommands, etc.) ne sont pas autorisées.
Attente et cycle de vie de la session
Après chaque execute, le devenir de la session dépend de expect et de la présence de boutons inline avec callback_data dans la commande :
expect=textouany— le prochain message de l'utilisateur sera transmis au service via l'événementmessage.expect=noneET la commande ne contenait PAS de boutons inline avec callback — la session se ferme automatiquement. C'est un message « terminal ».expect=none, MAIS des boutons callback sont présents — la session reste ouverte : les appuis sont capturés tant que les tokens des boutons sont valides (jusqu'au TTL).
Important : expect n'influence pas la réception des appuis sur les boutons. Les appuis sur les boutons tokenisés sont capturés indépendamment de la valeur de expect — tant que la session et le token du bouton sont valides. expect contrôle uniquement le fait que le service attend ou non une réponse textuelle/quelconque.
Frontières du dialogue
Le contexte du dialogue vit exactement jusqu'à la fermeture de la session :
- Réponse textuelle lors d'une attente active (
expect=text/any) — est transmise au service via l'événementmessage, et non via les réactions habituelles du bot. - Appui sur un bouton tokenisé — est transmis au service via l'événement
callbackavec lecallback_dataoriginal. GetMyBot confirme toujours lui-même l'appui (answerCallbackQuery) ; l'appartenance de l'appui est vérifiée (bot + utilisateur + chat de la session). Un appui sur un bouton ne ferme pas la session. /cancel(ainsi que/cancel@botet « annuler ») lors d'une session externe active — la session se ferme,session.cancelest envoyé au service, « Annulé. » est envoyé à l'utilisateur.- Un dialogue par utilisateur. L'ouverture d'une nouvelle session supplante l'ancienne session ouverte de cet utilisateur —
session.cancellui est envoyé. - Expiration du TTL. La session est marquée
expiredpar le minuteur et (si le service est en ligne)session.expiredlui est envoyé.
Limites et délais d'expiration
- Taille de trame : 256 KiB.
- TTL de session : 3600 secondes par défaut (configurable via le paramètre
session_ttl_secondsde l'intégration). - Fréquence des commandes : 60
executepar minute par session (fenêtre glissante). - Méthodes dans un seul
execute: jusqu'à 30 (les méthodes supplémentaires sont abandonnées). - Sessions ouvertes par intégration : jusqu'à 1000.
- File d'attente hors ligne : jusqu'à 100 événements par session.
- Heartbeat :
pingtoutes les 30 secondes ; délai d'expiration en cas d'inactivité — 60 secondes. - Délai d'expiration de la trame d'authentification : 10 secondes.
Fiabilité (heartbeat, hors ligne, file d'attente, fallback)
- Heartbeat. Le serveur envoie une trame
{"type":"ping"}toutes les 30 secondes. Si le client n'a pas eu d'activité depuis plus de 60 secondes, la connexion est fermée. Le client peut envoyer ses propres tramesping/pongpour maintenir l'activité. Une interruption de connexion ne tue pas les dialogues : les sessions survivent en base de données jusqu'au TTL ou à la reconnexion. - La reconnexion est à la charge du service. Si la connexion est interrompue, votre service doit se reconnecter. Les sessions ouvertes en base de données survivent aux redémarrages jusqu'à l'expiration du TTL.
- Fallback en cas d'absence lors du démarrage. Si le service n'est pas connecté au moment du déclenchement de la réaction, la réaction de secours définie dans les paramètres de l'intégration est lancée (
on_unavailable_reaction_id). Si elle n'est pas définie, il ne se passe rien (silencieusement). - File d'attente hors ligne. Les événements
message/callbacknon délivrés en raison d'une absence sont placés dans la file d'attente hors ligne (jusqu'à 100 événements, FIFO, conservés jusqu'à l'expiration de la session) et renvoyés lors de la reconnexion. Les événementssession.open/session.cancel/session.expiredne sont pas mis en file d'attente. - Garantie de livraison — at-most-once. En cas de dépassement ou d'expiration de la file d'attente, les événements sont abandonnés. Concevez votre logique de façon à ce que la perte d'un événement isolé ne brise pas le scénario.
Indicateur en ligne
Il est possible de vérifier si le service maintient une connexion active via l'endpoint :
GET /api/bots/{botID}/integrations/{integrationID}/status
Réponse :
{ "online": true }
Le même indicateur est disponible sur la carte d'intégration dans le tableau de bord. L'équivalent en MCP est l'outil get_integration_status.
Sécurité
GetMyBot exécute les commandes d'un service tiers avec son propre token, la protection est donc stricte.
- Pas de surface d'attaque SSRF. GetMyBot agit en tant que serveur WebSocket et ne fait pas lui-même de connexions sortantes vers le service — c'est le service qui se connecte à GetMyBot. Il n'y a pas d'URL contrôlable depuis l'extérieur vers laquelle la plateforme se rendrait.
- Le token est stocké sous forme de hash (sha256), la vérification est à durée constante, la rotation est prise en charge.
- Isolation des locataires. Une commande est strictement liée à sa propre intégration ; une session est liée au bot + utilisateur + chat. Les événements de bots tiers n'arrivent pas dans votre socket.
- Liste blanche des méthodes +
chat_idforcé empêchent de transformer le bot en expéditeur vers des chats arbitraires. - Le token du bot n'est pas transmis à l'extérieur — le service ne communique jamais directement avec Telegram.
- Masquage des secrets. Le token d'intégration et les valeurs sensibles sont masqués dans le journal et les logs.
Exemples
Gestionnaire minimal : à l'ouverture d'un dialogue, envoie un message et ferme la session (expect: "none"). Remplacez l'id de l'intégration et le token. L'adresse doit obligatoirement contenir <integrationID> dans le chemin.
Node.js
import WebSocket from "ws";
const URL = `wss://api.mybot.app/ext/ws/${process.env.MYBOT_INTEGRATION_ID}`;
const ws = new WebSocket(URL, {
headers: { Authorization: `Bearer ${process.env.MYBOT_TOKEN}` },
});
ws.on("message", (raw) => {
const ev = JSON.parse(raw);
if (ev.type === "session.open") {
ws.send(JSON.stringify({
type: "execute",
session_id: ev.session_id,
expect: "none",
methods: [{ method: "sendMessage", params: { text: "Bonjour depuis le gestionnaire externe !" } }],
}));
}
});
Python
import json, os, asyncio, websockets
async def main():
url = f"wss://api.mybot.app/ext/ws/{os.environ['MYBOT_INTEGRATION_ID']}"
headers = {"Authorization": f"Bearer {os.environ['MYBOT_TOKEN']}"}
async with websockets.connect(url, additional_headers=headers) as ws:
async for raw in ws:
ev = json.loads(raw)
if ev["type"] == "session.open":
await ws.send(json.dumps({
"type": "execute",
"session_id": ev["session_id"],
"expect": "none",
"methods": [
{"method": "sendMessage", "params": {"text": "Bonjour depuis le gestionnaire externe !"}}
],
}))
asyncio.run(main())
Bouton inline et traitement du callback
Pour capturer un appui, envoyez un bouton avec callback_data (GetMyBot le tokenise automatiquement). Avec expect: "none" et un bouton, la session reste ouverte tant que le token du bouton est valide — l'appui arrivera via l'événement callback, dans lequel event.callback_data est égal à la valeur originale. Il n'est pas nécessaire de confirmer l'appui (answerCallbackQuery) — GetMyBot le fait lui-même.
if (ev.type === "session.open") {
ws.send(JSON.stringify({
type: "execute",
session_id: ev.session_id,
expect: "none",
methods: [{
method: "sendMessage",
params: {
text: "Appuyez sur le bouton",
reply_markup: { inline_keyboard: [[{ text: "C'est parti", callback_data: "go" }]] },
},
}],
}));
} else if (ev.type === "callback" && ev.event.callback_data === "go") {
ws.send(JSON.stringify({
type: "execute",
session_id: ev.session_id,
expect: "none",
methods: [{ method: "sendMessage", params: { text: "Bouton appuyé !" } }],
}));
}
Et ensuite
- Intégrations — vue d'ensemble des services connectables.
- Réactions — comment sont construits les déclencheurs, conditions et actions.
- Requêtes web et webhooks — moyen plus simple d'appeler une URL externe sans dialogue.