Construire sur GPChat
Créez des bots qui discutent avec vos utilisateurs, automatisez des groupes et des listes de diffusion, publiez des stories et connectez vos services au compte GPChat de vos utilisateurs.
URL de base
| Environnement | URL | Console |
|---|---|---|
| Production | https://api.gpchat.app | console.html |
Il n'existe pas d'environnement de staging ouvert aux développeurs tiers : l'app mobile GPChat distribuée sur les stores ne peut se connecter qu'à la production, il n'y a donc aucun moyen d'échanger de vrais messages ailleurs qu'en production. Pour tester sans risque, voir la FAQ : votre bot n'est trouvable par personne tant que vous n'avez pas partagé son nom d'utilisateur, vous pouvez donc développer et tester directement en production, avec votre propre compte.
Démarrage rapide
- Connectez-vous à la console développeur en scannant le QR code avec l'app GPChat (Paramètres → GPChat pour Développeurs → Connexion Web), ou avec Google.
- Créez une application avec « Créer le bot » coché. Copiez immédiatement la clé API (
gpk_live_…) et le secret de webhook : ils ne seront plus réaffichés.Depuis votre téléphone, vous pouvez aussi écrire à @gpchat_forge et taper
/newbot. - Exposez un endpoint HTTPS qui reçoit les webhooks, puis renseignez son URL dans l'onglet Webhooks et cliquez sur « Envoyer un ping ».
- Écrivez à votre bot depuis l'app GPChat (recherchez son nom d'utilisateur). Votre serveur reçoit un événement
message.received. - Répondez avec l'API, en utilisant
data.chatIdcomme destinataire :
import express from 'express';
import crypto from 'node:crypto';
const app = express();
const { GPCHAT_API_KEY, GPCHAT_WEBHOOK_SECRET } = process.env;
// Corps BRUT requis pour vérifier la signature
app.post('/gpchat/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
if (!verify(req.body, req.get('X-GPChat-Signature'), GPCHAT_WEBHOOK_SECRET)) {
return res.sendStatus(401);
}
res.sendStatus(200); // accuser réception tout de suite (< 10 s)
const { type, data } = JSON.parse(req.body);
if (type === 'message.received' && data.textMsg === '/start') {
await fetch('https://api.gpchat.app/v1/messages', {
method: 'POST',
headers: { Authorization: `Bearer ${GPCHAT_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ to: data.chatId, type: 'text', content: 'Bienvenue ! 👋' }),
});
}
});
function verify(rawBody, header, secret) {
const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || '');
if (!m || Math.abs(Date.now() / 1000 - Number(m[1])) > 300) return false;
const expected = crypto.createHmac('sha256', secret).update(`${m[1]}.${rawBody}`).digest();
return crypto.timingSafeEqual(expected, Buffer.from(m[2], 'hex'));
}
app.listen(3000);
import hmac, hashlib, json, os, re, time
import requests
from flask import Flask, request, abort
app = Flask(__name__)
API_KEY = os.environ["GPCHAT_API_KEY"]
SECRET = os.environ["GPCHAT_WEBHOOK_SECRET"].encode()
def verify(raw: bytes, header: str) -> bool:
m = re.fullmatch(r"t=(\d+),v1=([0-9a-f]{64})", header or "")
if not m or abs(time.time() - int(m[1])) > 300:
return False
expected = hmac.new(SECRET, f"{m[1]}.".encode() + raw, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, m[2])
@app.post("/gpchat/webhook")
def webhook():
raw = request.get_data() # corps brut
if not verify(raw, request.headers.get("X-GPChat-Signature")):
abort(401)
event = json.loads(raw)
if event["type"] == "message.received" and event["data"].get("textMsg") == "/start":
requests.post(
"https://api.gpchat.app/v1/messages",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"to": event["data"]["chatId"], "type": "text", "content": "Bienvenue ! 👋"},
timeout=10,
)
return "", 200
curl -X POST https://api.gpchat.app/v1/messages \
-H "Authorization: Bearer $GPCHAT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"to": "USER_ID", "type": "text", "content": "Bienvenue ! 👋"}'
Concepts clés
| Concept | Description |
|---|---|
| Application | Conteneur de vos identifiants : App ID (= client_id OAuth2), client_secret, clé(s) API, webhook et secret de signature. 20 applications maximum par compte. |
| Bot | Un vrai compte GPChat (isBot: true) rattaché à une application (un seul par application). Il reçoit et envoie des messages comme un utilisateur, en privé ou dans les groupes dont il est membre. |
| Clé API | gpk_live_… — identifie le bot. Scopes par défaut : messages:send, groups:manage. Seule son empreinte SHA-256 est stockée : elle n'est affichée qu'une fois. |
| Token OAuth2 | gpat_… (accès, 1 h) et gprt_… (rafraîchissement, 30 jours) — permettent d'agir au nom d'un utilisateur qui a donné son consentement. |
| Webhook | URL HTTPS de votre serveur, appelée à chaque message reçu par votre bot. Signée en HMAC-SHA256. |
| userId | Identifiant stable d'un utilisateur GPChat. Vous l'obtenez via les webhooks (from, chatId) ou via /oauth/userinfo (sub). |
Authentification
Chaque requête porte un jeton dans l'en-tête Authorization :
Authorization: Bearer gpk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
| Endpoint | Clé API (bot) | Token OAuth2 (utilisateur) | Scope requis |
|---|---|---|---|
/v1/messages, /v1/messages/group | ✅ | — | messages:send |
/v1/groups/* | ✅ | ✅ | groups:manage |
/v1/stories, /v1/stories/delete | — | ✅ | stories:write |
/v1/stories/mine | — | ✅ | stories:read |
/v1/polls/* | ✅ | ✅ | polls:write |
/oauth/userinfo | — | ✅ | profile |
Ne jamais exposer une clé API ou un client_secret côté client (navigateur, app mobile, dépôt de code). Appelez l'API depuis votre serveur uniquement.
Créer et gérer un bot
Dans la console, onglet Bot :
- Description (512 caractères) — affichée sur la fiche du bot et à l'ouverture de la conversation.
- Commandes (50 max) — proposées dans le menu
/de l'app. Nom : minuscules, chiffres et_(32 caractères) ; description : 256 caractères. - Nom affiché — suit le nom de l'application (onglet Paramètres).
Avec @gpchat_forge sur mobile : /newbot, /mybots, /token, /setdescription, /setcommands, /setwebhook, /cancel. Le nom d'utilisateur doit se terminer par bot (5 à 32 caractères, minuscules/chiffres/_) et ne peut pas contenir « gpchat ».
/token révoque immédiatement la clé en cours et en émet une nouvelle.
Envoyer des messages
Message privé. Champ to = userId du destinataire, plus les champs du type choisi.
Message de groupe. Champ groupId à la place de to. Le bot doit être membre (et administrateur si le groupe est en « seuls les admins écrivent »).
Réponse — 201 Created
{
"status": "success",
"message": "Opération réussie",
"data": { "msgId": "8f14e45f-ceea-4e7b-9a4c-1b2c3d4e5f60" },
"timestamp": 1735000000000
}
Conservez msgId : il sert à citer ce message plus tard (replyToMsgId) et à le retrouver dans les webhooks.
Tous les types de messages
Les messages de votre bot s'affichent dans l'app exactement comme ceux d'un utilisateur. Toutes les URL doivent être en https:// et publiquement accessibles : l'app les télécharge directement (hébergez-les sur votre CDN, S3, Cloudinary…).
| type | Champs requis | Optionnels | Rendu dans l'app |
|---|---|---|---|
text | content (≤ 4 096) | — | Bulle de texte, liens cliquables |
image | fileUrl | caption (≤ 1 024) | Photo, ouverture plein écran |
video | fileUrl, thumbnailUrl | caption | Miniature + lecteur vidéo |
audio | fileUrl | — | Lecteur audio |
doc | fileUrl | caption | Carte document téléchargeable |
gif | fileUrl | — | GIF animé |
sticker | fileUrl (PNG/WebP transparent) | — | Sticker sans bulle |
location | location.latitude, location.longitude | location.name, location.address (≤ 256) | Carte avec itinéraire |
Tous les types acceptent replyToMsgId. Pour un sondage, utilisez /v1/polls.
Texte
{ "to": "USER_ID", "type": "text", "content": "Votre colis arrive demain entre 9h et 12h 🚚" }
Image avec légende
{ "to": "USER_ID", "type": "image", "fileUrl": "https://cdn.example.com/recu-042.jpg", "caption": "Votre reçu" }
Vidéo
{
"to": "USER_ID",
"type": "video",
"fileUrl": "https://cdn.example.com/tuto.mp4",
"thumbnailUrl": "https://cdn.example.com/tuto.jpg",
"caption": "Tutoriel en 60 secondes"
}
Audio
{ "to": "USER_ID", "type": "audio", "fileUrl": "https://cdn.example.com/annonce.m4a" }
Document
{ "to": "USER_ID", "type": "doc", "fileUrl": "https://cdn.example.com/facture-042.pdf", "caption": "Facture n°042" }
GIF et sticker
{ "to": "USER_ID", "type": "gif", "fileUrl": "https://media.giphy.com/media/l0MYt5jPR6QX5pnqM/giphy.gif" }
{ "to": "USER_ID", "type": "sticker", "fileUrl": "https://cdn.example.com/stickers/merci.webp" }
Position
{
"to": "USER_ID",
"type": "location",
"location": { "latitude": 4.0511, "longitude": 9.7679, "name": "Agence Akwa", "address": "Bd de la Liberté, Douala" }
}
Formats recommandés : images JPEG/PNG/WebP (≤ 10 Mo), vidéos MP4 H.264 (≤ 50 Mo), audio M4A/MP3, documents PDF. L'API ne télécharge pas vos fichiers : c'est l'app de l'utilisateur qui le fait.
Réponses et mentions
Citer un message (réponse)
Ajoutez replyToMsgId : le message cité apparaît au-dessus de votre réponse. Il doit appartenir à la même conversation (sinon 404). Utilisez le msgId reçu dans le webhook.
{ "to": "USER_ID", "type": "text", "content": "C'est noté, je m'en occupe !", "replyToMsgId": "MSG_ID_RECU" }
Mentionner des membres (groupes)
mentionedUserIds (50 max) — uniquement dans un groupe, et uniquement des membres du groupe.
{ "groupId": "GROUP_ID", "type": "text", "content": "@Alice ton ticket est résolu ✅", "mentionedUserIds": ["USER_ID_ALICE"] }
Règles et anti-spam
Pour protéger les utilisateurs, les bots suivent des règles strictes, vérifiées côté serveur :
| Règle | Erreur |
|---|---|
| Un bot ne peut écrire en privé qu'à un utilisateur qui lui a déjà écrit au moins une fois. Un bot répond, il ne démarche pas. | 400 failed-precondition |
| Aucun message à un utilisateur qui a bloqué le bot. | 403 permission-denied |
| Un bot ne peut pas écrire à un autre bot (évite les boucles). | 403 permission-denied |
| Un bot ne peut ajouter à un groupe (création ou ajout de membres) que des utilisateurs qui lui ont déjà écrit. | 403 permission-denied |
| Un bot par application, 20 applications par compte. | 409 / 429 |
Pour permettre à vos utilisateurs de démarrer une conversation, partagez le nom d'utilisateur de votre bot ou publiez-le dans le Store des bots. La première action de l'utilisateur (même /start) ouvre la conversation.
Webhooks
Configurez l'URL dans la console (onglet Webhooks). GPChat y envoie un POST JSON pour chaque message reçu par votre bot.
- URL en
https://, publique : les adresses privées, locales ou internes sont refusées, et les redirections ne sont pas suivies. - Répondez en 2xx en moins de 10 secondes. Traitez le message de manière asynchrone si besoin.
- Un nouveau bot peut mettre jusqu'à 5 minutes à recevoir ses premiers webhooks.
En-têtes
| En-tête | Valeur |
|---|---|
Content-Type | application/json |
X-GPChat-Event | message.received, group_message.received ou ping |
X-GPChat-Signature | t=<timestamp_unix>,v1=<hmac_sha256_hex> |
Événements et charge utile
Le corps a toujours la forme { "type": "<événement>", "data": { … } }.
message.received
{
"type": "message.received",
"data": {
"msgId": "8f14e45f-ceea-4e7b-9a4c-1b2c3d4e5f60",
"type": "text",
"textMsg": "/start",
"from": "USER_ID",
"chatId": "USER_ID",
"sentAt": 1735000000000
}
}
group_message.received
{
"type": "group_message.received",
"data": {
"msgId": "MSG_ID",
"type": "image",
"textMsg": "",
"fileUrl": "https://…/photo.jpg",
"caption": "Regardez ça",
"from": "USER_ID",
"groupId": "GROUP_ID",
"mentionedUserIds": ["BOT_USER_ID"],
"isMentioned": true,
"replyToMsgId": "MSG_ID_CITE",
"sentAt": 1735000000000
}
}
| Champ | Description |
|---|---|
msgId | Identifiant unique du message. Utilisez-le pour dédoublonner : une même livraison peut être rejouée. |
type | text, image, video, audio, doc, gif, sticker, location, poll |
textMsg | Texte en clair (déchiffré par GPChat avant l'envoi). |
fileUrl, caption, gifUrl, videoThumbnail | Médias, selon le type. |
location | { latitude, longitude, name?, address? } |
poll | Sondage (question, options). |
replyToMsgId | Message cité par l'utilisateur, le cas échéant. |
from | userId de l'expéditeur. |
chatId | Messages privés : à utiliser comme to pour répondre. |
groupId, mentionedUserIds, isMentioned | Messages de groupe ; isMentioned vaut true si votre bot est mentionné. |
sentAt | Date d'envoi (epoch ms). |
sandbox | true uniquement pour un événement simulé depuis la console. |
Vérifier la signature
La signature est un HMAC-SHA256 de {timestamp}.{corps brut}, calculé avec le secret de webhook de votre application. Vérifiez-la avant tout traitement, sur le corps brut (jamais sur un JSON re-sérialisé), en temps constant, et rejetez un timestamp de plus de 5 minutes (protection contre le rejeu).
import crypto from 'node:crypto';
export function verifyGpchatSignature(rawBody, header, secret, toleranceSec = 300) {
const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || '');
if (!m) return false;
if (Math.abs(Date.now() / 1000 - Number(m[1])) > toleranceSec) return false;
const expected = crypto.createHmac('sha256', secret).update(`${m[1]}.${rawBody}`).digest();
return crypto.timingSafeEqual(expected, Buffer.from(m[2], 'hex'));
}
import hashlib, hmac, re, time
def verify_gpchat_signature(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
m = re.fullmatch(r"t=(\d+),v1=([0-9a-f]{64})", header or "")
if not m or abs(time.time() - int(m[1])) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{m[1]}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, m[2])
<?php
function verifyGpchatSignature(string $rawBody, ?string $header, string $secret, int $tolerance = 300): bool {
if (!preg_match('/^t=(\d+),v1=([0-9a-f]{64})$/', $header ?? '', $m)) return false;
if (abs(time() - (int) $m[1]) > $tolerance) return false;
$expected = hash_hmac('sha256', $m[1] . '.' . $rawBody, $secret);
return hash_equals($expected, $m[2]);
}
// $rawBody = file_get_contents('php://input');
// $header = $_SERVER['HTTP_X_GPCHAT_SIGNATURE'] ?? null;
func VerifyGpchatSignature(rawBody []byte, header, secret string) bool {
re := regexp.MustCompile(`^t=(\d+),v1=([0-9a-f]{64})$`)
m := re.FindStringSubmatch(header)
if m == nil {
return false
}
ts, _ := strconv.ParseInt(m[1], 10, 64)
if math.Abs(float64(time.Now().Unix()-ts)) > 300 {
return false
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(m[1] + "."))
mac.Write(rawBody)
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(m[2]))
}
Réessais et monitoring
En cas d'échec (code non-2xx, délai dépassé, erreur réseau), GPChat réessaie avec un repli exponentiel : 30 s, 1 min, 2 min, 4 min… jusqu'à 1 h entre deux essais, soit 8 tentatives. Au-delà, l'événement est marqué abandoned et vos contacts d'alerte (e-mail, webhook Slack/Discord) sont prévenus.
L'onglet Monitoring de la console affiche le taux de succès, la latence moyenne de votre serveur, l'activité horaire, la consommation du quota et le journal détaillé des 100 derniers événements (charge utile, réponse de votre serveur, signature), exportable en JSON ou CSV.
Groupes & diffusion
Acteur : votre bot (clé API) ou un utilisateur (token OAuth2, scope groups:manage). Le créateur devient l'unique administrateur. Un groupe isBroadcast: true est une liste de diffusion, où seul son créateur écrit.
| Méthode | Endpoint | Corps | Droits |
|---|---|---|---|
| POST | /v1/groups | name (≤ 100), description? (≤ 500), members? (≤ 100), isBroadcast?, photoUrl? | Tous |
| DELETE | /v1/groups/delete | groupId | Admin |
| POST | /v1/groups/members | groupId, memberIds (1 à 100) | Admin |
| POST | /v1/groups/members/remove | groupId, memberId | Admin, ou soi-même (quitter) |
| PATCH | /v1/groups/members/role | groupId, memberId, isAdmin | Admin (hors diffusion) |
| PATCH | /v1/groups/details | groupId + name, description, photoUrl, sendMessages, editInfoOnlyAdmin | Admin |
| POST | /v1/groups/broadcast | groupId + champs d'un message | Membre (créateur pour une diffusion) |
curl -X POST https://api.gpchat.app/v1/groups \
-H "Authorization: Bearer $GPCHAT_API_KEY" -H "Content-Type: application/json" \
-d '{"name": "Clients Premium", "isBroadcast": true, "members": ["USER_ID_1", "USER_ID_2"]}'
Stories
Token OAuth2 uniquement : une story est toujours publiée au nom de l'utilisateur qui a donné son consentement.
| Méthode | Endpoint | Détails | Scope |
|---|---|---|---|
| POST | /v1/stories | type = text (text ≤ 1 000), image (imageUrl) ou video (videoUrl, thumbnailUrl?) | stories:write |
| GET | /v1/stories/mine | Stories de l'utilisateur | stories:read |
| DELETE | /v1/stories/delete | storyId (appartenant à l'utilisateur) | stories:write |
Sondages
Clé API (bot) ou token OAuth2 — scope polls:write dans les deux cas. Cible : groupId (l'acteur doit être membre) ou receiverId (conversation privée). Seul le créateur du sondage peut le clore.
| Méthode | Endpoint | Corps |
|---|---|---|
| POST | /v1/polls | question (≤ 300), options (2 à 12, ≤ 100 chacune), allowMultipleAnswers?, isAnonymous? |
| POST | /v1/polls/vote | messageId, optionId — bascule le vote |
| POST | /v1/polls/close | messageId — créateur uniquement |
{
"groupId": "GROUP_ID",
"question": "Quel créneau pour la réunion ?",
"options": ["Lundi 10h", "Mardi 14h", "Jeudi 16h"],
"allowMultipleAnswers": true
}
Login with GPChat (OAuth2)
Permettez à vos utilisateurs de se connecter à votre service avec leur compte GPChat, puis d'agir en leur nom (stories, sondages, groupes). Flux Authorization Code (RFC 6749).
Scopes
| Scope | Accès |
|---|---|
profile | Nom et photo de profil (/oauth/userinfo) |
stories:read / stories:write | Lire / publier et supprimer les stories de l'utilisateur |
polls:write | Créer des sondages, voter, clôturer |
groups:manage | Créer et administrer des groupes et diffusions |
- Enregistrez vos URI de redirection (console → Identifiants). Correspondance exacte ;
https://obligatoire (http://localhostaccepté en développement). - Redirigez l'utilisateur vers la page de consentement, avec un
statealéatoire mémorisé en session :https://api.gpchat.app/oauth/authorize ?response_type=code &client_id=APP_ID &redirect_uri=https%3A%2F%2Fmonapp.com%2Fcallback &scope=profile%20stories%3Awrite &state=RANDOM_CSRF_TOKEN - Récupérez le code : l'utilisateur revient sur
redirect_uri?code=…&state=…(ou?error=access_denied). Vérifiez questatecorrespond. - Échangez le code depuis votre serveur (usage unique, valide 5 minutes) :
curl -X POST https://api.gpchat.app/oauth/token -H "Content-Type: application/json" -d '{ "grant_type": "authorization_code", "code": "CODE", "redirect_uri": "https://monapp.com/callback", "client_id": "APP_ID", "client_secret": "CLIENT_SECRET" }' # → { "access_token": "gpat_…", "refresh_token": "gprt_…", "token_type": "Bearer", "expires_in": 3600, "scope": "profile stories:write" } - Appelez l'API avec
Authorization: Bearer gpat_…, par exempleGET /oauth/userinfo→{ "sub", "name", "picture" }. - Rafraîchissez avant expiration (1 h) avec
grant_type=refresh_token. Chaque rafraîchissement révoque l'ancien refresh token et en émet un nouveau (rotation) : stockez toujours le dernier reçu. - Déconnexion :
POST /oauth/revokeavec{ token, client_id, client_secret }révoque le token et toute sa famille.
Si un refresh token déjà utilisé est présenté de nouveau, la requête échoue (401) : c'est le signe d'un vol possible. Redemandez alors le consentement de l'utilisateur.
Limites et quotas
| Ressource | Limite |
|---|---|
| Requêtes API | 60 / minute par défaut, par clé API et par endpoint (par application pour les tokens OAuth2). Quota relevable sur demande. |
| Applications | 20 par compte (hors supprimées) |
| Bot | 1 par application |
| Texte | 4 096 caractères ; légende 1 024 |
| URL | 2 048 caractères, https:// |
| Mentions | 50 par message |
| Membres ajoutés | 100 par requête (taille maximale du groupe vérifiée côté serveur) |
| Webhook | Réponse attendue en moins de 10 s ; 8 tentatives |
Au-delà du quota : 429. Réessayez après quelques secondes avec un repli exponentiel (1 s, 2 s, 4 s…). La consommation en temps réel est visible dans la console (Identifiants → Consommation du quota).
Erreurs
Toute erreur renvoie un code HTTP et un corps uniforme :
{ "status": "error", "code": "invalid-argument", "message": "Champ 'fileUrl' : seules les URL https:// sont acceptées" }
| HTTP | code | Causes fréquentes | Que faire |
|---|---|---|---|
| 400 | invalid-argument | Champ manquant ou invalide, URL non https, type inconnu | Corriger la requête (le message indique le champ) |
| 400 | failed-precondition | L'utilisateur n'a jamais écrit au bot ; sondage clos | Attendre que l'utilisateur écrive au bot |
| 401 | unauthenticated | Jeton absent, invalide, révoqué ou expiré | Vérifier l'en-tête ; rafraîchir le token OAuth2 |
| 403 | permission-denied | Scope manquant ; application désactivée/suspendue ; bot bloqué ; pas administrateur | Vérifier les droits et le statut de l'app |
| 404 | not-found | Destinataire, groupe ou message cité introuvable | Vérifier les identifiants |
| 409 | already-exists | L'application a déjà un bot | Régénérer la clé plutôt que recréer |
| 422 | failed-precondition | Clé API sans bot | Créer le bot dans la console |
| 429 | RATE_LIMIT_EXCEEDED / resource-exhausted | Quota dépassé ; limite d'apps ou de membres | Repli exponentiel |
| 500 | INTERNAL_ERROR | Erreur inattendue | Réessayer ; si ça persiste, contactez-nous avec l'heure et l'endpoint |
Désactiver ou supprimer une application
Console → onglet Paramètres → Zone de danger.
| Désactiver | Supprimer | |
|---|---|---|
| Réversible | ✅ Oui (bouton « Réactiver ») | ❌ Non |
| Clés API | Refusées (403), conservées | Révoquées |
| Tokens OAuth2 | Refusés (403), conservés | Révoqués |
| Webhooks | Plus aucun envoi | URL et secret retirés |
| Bot | Inchangé | Désactivé : ne peut plus se connecter, profil marqué supprimé |
| Messages déjà envoyés | Conservés | Conservés (restent visibles pour les utilisateurs) |
Une application suspendue par l'équipe GPChat ne peut pas être réactivée depuis la console : contactez infos@gpchat.app.
Bonnes pratiques de sécurité
- Secrets côté serveur uniquement, dans des variables d'environnement ou un gestionnaire de secrets, jamais dans le code source.
- Vérifiez chaque signature de webhook sur le corps brut, en temps constant, avec une tolérance de 5 minutes.
- Dédoublonnez par
msgId: une livraison peut arriver deux fois. - Régénérez immédiatement une clé ou un secret exposé (console → Identifiants) ; en cas de doute, désactivez l'application.
- OAuth2 : utilisez toujours
state, stockez les tokens chiffrés, demandez le minimum de scopes. - Ne faites pas confiance au contenu des messages reçus : échappez-le avant de l'afficher, validez-le avant de l'exécuter.
- Hébergez vos médias sur un domaine que vous contrôlez, en HTTPS.
FAQ
Mon bot ne reçoit pas de webhooks.
Vérifiez (1) que l'URL est enregistrée et répond au ping, (2) que l'application est active, (3) que vous attendez au plus 5 minutes après la création du bot, (4) le journal de l'onglet Monitoring pour voir les réponses de votre serveur.
Je reçois failed-precondition en envoyant un message.
L'utilisateur n'a jamais écrit à votre bot. Demandez-lui de lui envoyer un premier message (par exemple /start).
Où trouver le userId d'un utilisateur ?
Dans chaque webhook (from, chatId), ou via /oauth/userinfo (sub) pour un utilisateur connecté avec Login with GPChat.
Puis-je tester sans risque, sachant qu'il n'y a pas de staging ?
Oui, directement en production : utilisez le simulateur d'événements de la console (aucun utilisateur réel impliqué), puis écrivez à votre bot depuis votre propre compte GPChat habituel. Tant que vous n'avez partagé son nom d'utilisateur avec personne, vous êtes la seule personne au monde capable de le trouver — le tester ainsi n'affecte jamais d'autres utilisateurs.
Mon texte apparaît-il chiffré dans les webhooks ?
Non. Les messages privés sont chiffrés au repos, mais GPChat vous transmet toujours textMsg en clair.
Nouveautés
v1.1 — septembre 2026
- Nouveaux types de messages pour les bots :
video,gif,sticker,location; réponses citées (replyToMsgId) et mentions en groupe. - Webhooks : texte des messages privés transmis en clair ; nouveaux champs (
gifUrl,videoThumbnail,location,replyToMsgId,isMentioned,sentAt) ; code HTTP et latence réels dans le monitoring. - Console : désactivation et suppression d'application, modification des URI de redirection, contacts d'alerte opérationnels.
- Sécurité : règles anti-spam pour les bots, protection SSRF des webhooks, validation stricte des entrées.
- Nouvelle référence interactive OpenAPI 3.1 (openapi.json).
- Nouveau tutoriel complet : construire un bot IA de A à Z en Node.js/TypeScript, du sandbox à la production.