Depuis votre serveur : lire les retours, répondre, changer un statut, supprimer un utilisateur. Et recevoir chaque événement, signé, à l'adresse de votre choix.
Authentification
Toutes les adresses commencent par https://talkieback.com/api/server/v1. Chaque appel porte le secret de l'app dans l'en-tête Authorization. Le secret commence par sk_live_ : il se crée sur la page « Identité vérifiée » ou dans Réglages, Pour les développeurs, et ne va que sur votre serveur, jamais dans l'app.
Les réponses sont en JSON. Un secret régénéré laisse l'ancien valable 24 h, le temps de mettre à jour votre serveur.
Limites
60 requêtes par minute et par app, et un quota par jour qui dépend de l'offre. Au-delà, la réponse est 429 : réessayez un peu plus tard. Supprimer un utilisateur n'est jamais compté.
Requêtes par jour selon l'offre
Offre
Requêtes par jour
Gratuit
1 000
Solo
5 000
Pro
100 000
Business
500 000
Les retours
GET/feedbacks
Les retours de l'app, du plus récent au plus ancien.
Champ
Ce qu'il fait
status
Facultatif. Un statut : new, planned, in_progress, done, wont, spam.
limit
Facultatif. De 1 à 100 retours par page, 50 par défaut.
cursor
Facultatif. La valeur next_cursor de la page précédente.
Les erreurs : 401 secret absent ou refusé, 404 introuvable, 422 champ invalide (le message dit lequel), 429 trop de requêtes.
Webhooks
Ajoutez une adresse dans Réglages, Webhooks, et choisissez les événements. Talkieback y envoie un POST en JSON à chaque événement. Répondez 2xx en moins de 10 secondes ; sinon, Talkieback réessaie après 1 min, 5 min, 30 min, 2 h, 6 h et 12 h, pendant 24 h.
Les événements
Événement
Quand
feedback.created
Un retour arrive.
feedback.status_changed
Vous changez le statut d'un retour.
feedback.resolved
Un retour passe à « Fait » pour la première fois. Envoyé une seule fois par retour, avec la récompense.
feedback.rewarded
Votre équipe donne une récompense, au passage à « Fait » ou plus tard. Envoyé à chaque récompense : écoutez-le pour la créditer.
feedback.replied
Votre équipe répond à un utilisateur.
feedback.user_replied
Un utilisateur répond dans le fil.
Chaque envoi a la même forme : id (unique : un événement qui revient a le même), type, created_at et data.
Chaque envoi porte l'en-tête Talkieback-Signature: t=1761750642,v1=5f2b…. v1 est HMAC-SHA256(secret du webhook, t.corps) en hexadécimal, où t est l'heure d'envoi en secondes et corps le corps brut de la requête. Refusez une signature fausse, ou vieille de plus de 5 minutes.
Node (Express)
import { createHmac, timingSafeEqual } from'node:crypto';
import express from'express';
const app = express();
// le corps brut : la signature porte sur les octets reçus, pas sur le JSON relu
app.post('/webhooks/talkieback', express.raw({ type: 'application/json' }), (req, res) => {
const parts = Object.fromEntries(
(req.get('Talkieback-Signature') ?? '').split(',').map((p) => p.split('=')),
);
const expected = createHmac('sha256', process.env.TALKIEBACK_WEBHOOK_SECRET)
.update(`${parts.t}.${req.body}`)
.digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
const valid =
typeof parts.v1 === 'string' &&
parts.v1.length === expected.length &&
timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
if (!fresh || !valid) return res.status(400).send('Signature invalide');
const event = JSON.parse(req.body);
// event.id est unique : ignorez un événement déjà traitéif (event.type === 'feedback.rewarded') {
// la récompense à créditer : event.data.reward
}
res.sendStatus(200);
});
app.listen(3000);
Python (Flask)
import hashlib
import hmac
import os
import time
from flask import Flask, abort, request
app = Flask(__name__)
@app.post("/webhooks/talkieback")
def talkieback_webhook():
header = request.headers.get("Talkieback-Signature", "")
parts = dict(p.split("=", 1) for p in header.split(",") if"=" in p)
body = request.get_data()
expected = hmac.new(
os.environ["TALKIEBACK_WEBHOOK_SECRET"].encode(),
parts.get("t", "").encode() + b"." + body,
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(expected, parts.get("v1", "")):
abort(400)
if abs(time.time() - int(parts["t"])) > 300:
abort(400)
event = request.get_json()
# event["id"] est unique : ignorez un événement déjà traité
return"", 200
Offres Pro et Business. Votre serveur signe l'identifiant de chaque utilisateur connecté : hash = HMAC-SHA256(secret de l'app, id), en hexadécimal, sur l'identifiant en texte. Puis il passe l'identifiant et le hash au bouton. L'utilisateur retrouve ses retours sur tous ses appareils, et personne ne peut se faire passer pour un autre.
Le bouton parle français, anglais, allemand, italien, espagnol, néerlandais, portugais (Portugal et Brésil), tchèque, slovaque, polonais, hongrois, turc, russe, chinois, japonais et coréen. Avec la langue « Celle de l'utilisateur » (Réglages, Apparence), il prend la langue que vous lui donnez. Si vous n'en donnez pas, ou si elle n'est pas proposée, il prend celle de la page (<html lang>), puis celles du navigateur, et l'anglais si aucune n'est proposée. L'utilisateur peut la changer en bas du panneau.
Si votre app connaît la langue du compte, passez-la, avant ou après le chargement, ou dans identify (champ lang ou locale). La voix est transcrite dans la langue parlée, quelle que soit celle du panneau.
Sur un site, un navigateur n'accepte les notifications que d'un service worker de votre domaine. Ajoutez cette ligne à votre service worker, ou créez le fichier /talkieback-sw.js à la racine du site avec cette seule ligne :
Dans une app native, déposez votre jeton d'accès Expo, ou vos clés FCM et APNs, dans Réglages, Notifications. Le paquet enregistre l'appareil tout seul. Si votre app a déjà ses notifications, envoyez-les vous-même à la réception de feedback.replied.