API & Intégrations
Documentation pratique pour intégrer Sabi Workspace à vos systèmes. Connectez vos formulaires, sondages et billetterie à n'importe quel outil via notre API REST et nos webhooks.
Authentification
Toutes les requêtes à l'API v1 nécessitent une clé API transmise dans le header Authorization.
Obtenir une clé API
- Connectez-vous à SABI Workspace
- Allez dans Paramètres → Clés API
- Cliquez sur Nouvelle clé
- Choisissez un nom et les permissions (scopes) nécessaires
- Copiez la clé — elle ne sera affichée qu'une seule fois
Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Scopes disponibles
| Scope | Accès |
|---|---|
| forms:read | Lire les formulaires, sondages et événements |
| responses:read | Lire les réponses et soumissions |
| contacts:read | Lire les contacts et tags |
| contacts:write | Créer et modifier les contacts |
| tickets:read | Lire les tickets et types |
| webhooks:read | Lire les webhooks configurés |
| webhooks:write | Créer et modifier les webhooks |
| * | Accès complet (toutes les permissions) |
Démarrage en 3 minutes
- Créez une clé dans Paramètres → Clés API avec les scopes minimum nécessaires.
- Testez l'accès avec
GET /api/v1/forms. - Configurez un webhook dans Paramètres → Webhooks pour recevoir les événements métier.
curl "https://sabi.tools/api/v1/forms?limit=10" \ -H "Authorization: Bearer sk_votre_cle_api"
Formulaires
/api/v1/formsforms:readListe paginée de tous les formulaires, sondages et événements du projet.
Paramètres
| Nom | Type | Requis | Description |
|---|---|---|---|
| page | number | non | Numéro de page (défaut: 1) |
| limit | number | non | Résultats par page (défaut: 50, max: 100) |
| type | string | non | Filtrer par type : FORM, SURVEY ou TICKETING |
curl -X GET "https://sabi.tools/api/v1/forms?type=FORM&page=1&limit=10" \ -H "Authorization: Bearer sk_votre_cle_api"
{
"data": [
{
"id": "clxyz123...",
"title": "Formulaire d'inscription",
"type": "FORM",
"slug": "inscription-abc123",
"isPublished": true,
"isArchived": false,
"createdAt": "2026-01-15T10:00:00.000Z",
"updatedAt": "2026-01-20T14:30:00.000Z",
"_count": {
"responses": 42,
"questions": 8
}
}
],
"total": 12,
"page": 1,
"limit": 10
}Réponses
/api/v1/responsesresponses:readListe paginée des réponses soumises. Filtrable par formulaire et statut.
Paramètres
| Nom | Type | Requis | Description |
|---|---|---|---|
| page | number | non | Numéro de page (défaut: 1) |
| limit | number | non | Résultats par page (défaut: 50, max: 100) |
| formId | string | non | Filtrer par ID de formulaire |
| status | string | non | Filtrer par statut : NEW, HOT, WARM, COLD, PROCESSED |
curl -X GET "https://sabi.tools/api/v1/responses?formId=clxyz123&status=NEW&limit=20" \ -H "Authorization: Bearer sk_votre_cle_api"
{
"data": [
{
"id": "resp_abc123...",
"formId": "clxyz123...",
"status": "NEW",
"score": 85,
"submittedAt": "2026-08-15T09:30:00.000Z",
"answers": [
{ "questionId": "q1...", "value": "Maeva Fosso" },
{ "questionId": "q2...", "value": "maeva@example.com" }
],
"ticket": null
}
],
"total": 42,
"page": 1,
"limit": 20
}Statuts de réponse
Contacts
/api/v1/contactscontacts:readListe paginée des contacts du projet. Recherche par nom, email ou téléphone.
Paramètres
| Nom | Type | Requis | Description |
|---|---|---|---|
| page | number | non | Numéro de page |
| limit | number | non | Résultats par page (max: 100) |
| q | string | non | Recherche dans nom, email, téléphone |
| tagId | string | non | Filtrer par tag |
curl -X GET "https://sabi.tools/api/v1/contacts?q=william&limit=10" \ -H "Authorization: Bearer sk_votre_cle_api"
{
"data": [
{
"id": "contact_abc...",
"name": "William Akono",
"email": "william@example.com",
"phone": "+237699123456",
"tags": [
{ "id": "tag1", "name": "VIP", "color": "#8B5CF6" }
],
"_count": { "responses": 3, "tickets": 1 },
"createdAt": "2026-03-10T08:00:00.000Z"
}
],
"total": 156,
"page": 1,
"limit": 10
}/api/v1/contacts/:idcontacts:readDétail d'un contact avec ses tags, dernières réponses et tickets.
Paramètres
| Nom | Type | Requis | Description |
|---|---|---|---|
| id | string | oui | ID du contact |
curl -X GET "https://sabi.tools/api/v1/contacts/contact_abc123" \ -H "Authorization: Bearer sk_votre_cle_api"
{
"id": "contact_abc123",
"name": "Greg Hayatou",
"email": "greg@example.com",
"phone": "+237699123456",
"tags": [{ "id": "tag1", "name": "VIP", "color": "#8B5CF6" }],
"responses": [
{
"id": "resp_1",
"formId": "form_1",
"status": "HOT",
"score": 92,
"submittedAt": "2026-08-10T14:00:00.000Z"
}
],
"tickets": [
{
"id": "tkt_1",
"status": "VALID",
"createdAt": "2026-08-12T09:00:00.000Z",
"ticketType": { "name": "VIP Pass" }
}
]
}Tickets
/api/v1/ticketstickets:readListe paginée des tickets vendus. Filtrable par statut.
Paramètres
| Nom | Type | Requis | Description |
|---|---|---|---|
| page | number | non | Numéro de page |
| limit | number | non | Résultats par page (max: 100) |
| status | string | non | Filtrer par statut : VALID, SCANNED, CANCELLED |
curl -X GET "https://sabi.tools/api/v1/tickets?status=VALID&limit=20" \ -H "Authorization: Bearer sk_votre_cle_api"
{
"data": [
{
"id": "tkt_abc123",
"qrCode": "TKT-ABCDE12345",
"status": "VALID",
"createdAt": "2026-08-10T12:00:00.000Z",
"ticketType": {
"id": "type_1",
"name": "Standard",
"price": 5000,
"currency": "XAF"
},
"response": {
"id": "resp_1",
"submittedAt": "2026-08-10T12:00:00.000Z"
}
}
],
"total": 340,
"page": 1,
"limit": 20
}Webhooks
Les webhooks envoient automatiquement des données à votre URL chaque fois qu'un événement se produit dans votre projet. Configurez-les dans Paramètres → Webhooks.
Configuration
- Allez dans Paramètres → Webhooks
- Cliquez sur Nouveau webhook
- Entrez votre URL de réception (HTTPS recommandé)
- Sélectionnez les événements à écouter
- Un secret est généré automatiquement — conservez-le pour vérifier les signatures
Vérification de signature (HMAC-SHA256)
Chaque requête webhook inclut un header X-Sabi-Signaturecontenant le HMAC-SHA256 du body avec votre secret. Vérifiez-le pour confirmer l'authenticité.
const crypto = require("crypto");
function verifyWebhook(body, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(body, "utf8")
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Dans votre handler Express/Fastify :
app.post("/webhook/sabi", (req, res) => {
const signature = req.headers["x-sabi-signature"];
const isValid = verifyWebhook(
JSON.stringify(req.body),
signature,
process.env.SABI_WEBHOOK_SECRET
);
if (!isValid) return res.status(401).send("Invalid signature");
const { event, data } = req.body;
console.log("Événement reçu :", event, data);
res.status(200).json({ ok: true });
});import hmac, hashlib, json
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = "whsec_votre_secret"
@app.route("/webhook/sabi", methods=["POST"])
def handle_webhook():
signature = request.headers.get("X-Sabi-Signature", "")
body = request.get_data(as_text=True)
expected = hmac.new(
WEBHOOK_SECRET.encode(),
body.encode(),
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected):
abort(401)
payload = json.loads(body)
print(f"Événement: {payload['event']}", payload["data"])
return {"ok": True}Format de la requête webhook
POST https://votre-site.com/webhook/sabi
Content-Type: application/json
X-Sabi-Signature: a1b2c3d4e5f6...
{
"event": "response.created",
"timestamp": "2026-08-15T10:30:00.000Z",
"data": {
"id": "resp_abc123",
"formId": "form_xyz",
"formTitle": "Inscription événement",
"status": "NEW",
"score": 0,
"submittedAt": "2026-08-15T10:30:00.000Z",
"answers": [
{ "question": "Nom complet", "value": "Marie Ngo" },
{ "question": "Email", "value": "marie@example.com" },
{ "question": "Téléphone", "value": "+237670123456" }
]
}
}Types d'événements
Voici tous les événements disponibles pour les webhooks. Sélectionnez uniquement ceux dont vous avez besoin.
form.submitted— Formulaire soumis par un visiteurform.published— Formulaire publiéform.archived— Formulaire archivésurvey.completed— Sondage complétéresponse.created— Nouvelle réponse enregistréeresponse.updated— Statut de réponse modifiécontact.created— Nouveau contact créécontact.updated— Contact mis à jourcontact.merged— Contacts fusionnésticket.purchased— Ticket achetéticket.scanned— Ticket scanné (QR)ticket.cancelled— Ticket annuléticket.transferred— Ticket transférémember.invited— Membre invité au projetmember.removed— Membre retiré du projetinvoice.created— Facture crééeinvoice.paid— Facture payéeinvoice.overdue— Facture en retardExemples d'intégration
Google Sheets (automatique)
Chaque réponse ajoutée en temps réel
Utilisez un webhook + Google Apps Script pour synchroniser automatiquement les réponses.Voir le guide complet →
n8n / Make / Zapier
Automatisation no-code
Configurez un noeud Webhook dans votre outil d'automatisation, puis ajoutez l'URL générée dans Paramètres → Webhooks.
1. Noeud "Webhook" → URL: https://votre-n8n.com/webhook/sabi 2. Noeud "IF" → Filtrer par event === "response.created" 3. Noeud "Google Sheets" → Ajouter ligne avec les réponses 4. Noeud "Slack" → Envoyer notification au canal #leads
API personnalisée (CRM, ERP, etc.)
Intégration directe par code
// Script Node.js — s'exécute via CRON toutes les heures
const API_KEY = process.env.SABI_API_KEY;
const BASE = "https://sabi.tools/api/v1";
async function syncContacts() {
let page = 1;
let hasMore = true;
while (hasMore) {
const res = await fetch(
`${BASE}/contacts?page=${page}&limit=100`,
{ headers: { Authorization: `Bearer ${API_KEY}` } }
);
const { data, total } = await res.json();
for (const contact of data) {
await upsertToCRM({
name: contact.name,
email: contact.email,
phone: contact.phone,
tags: contact.tags.map(t => t.name),
totalResponses: contact._count.responses,
});
}
hasMore = page * 100 < total;
page++;
}
console.log("Synchronisation terminée !");
}
syncContacts();WhatsApp Business (notification)
Envoyer un message à chaque nouvelle réponse
// Handler webhook SABI
app.post("/webhook/sabi", async (req, res) => {
const { event, data } = req.body;
if (event === "ticket.purchased") {
const phone = data.answers.find(
a => a.question.includes("Téléphone")
)?.value;
if (phone) {
await fetch("https://waba.360dialog.io/v1/messages", {
method: "POST",
headers: {
"D360-API-KEY": process.env.DIALOG_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
messaging_product: "whatsapp",
to: phone,
type: "template",
template: {
name: "confirmation_billet",
language: { code: "fr" },
components: [{
type: "body",
parameters: [
{ type: "text", text: data.ticketType },
{ type: "text", text: data.qrCode },
],
}],
},
}),
});
}
}
res.json({ ok: true });
});SABI Hub (inter-apps)
SABI Hub permet l'interconnexion entre les applications de la suite SABI (Forms, SMS, Invoices, etc.). Les connexions utilisent l'authentification HMAC-SHA256 avec un secret partagé.
Créer une connexion
- Allez dans Paramètres → Connexions SABI
- Cliquez sur Nouvelle connexion
- Entrez l'App ID et l'URL de l'application cible
- Sélectionnez les scopes de données partagées
- Conservez le secret HMAC pour signer vos requêtes
Headers d'authentification Hub
X-Sabi-App-Id: votre_app_id X-Sabi-Signature: hmac_sha256_du_body X-Sabi-Timestamp: 2026-08-15T10:30:00.000Z
Scopes Hub disponibles
prospects:write— Créer des prospectscontacts:read— Lire les contactscontacts:write— Créer/modifier les contactsemail:send— Envoyer des emailsemail:templates— Accéder aux templates emailresponses:read— Lire les réponsesforms:read— Lire les formulairesLimites & quotas
| Ressource | Gratuit | Payant |
|---|---|---|
| Formulaires actifs | Illimité | Illimité |
| Événements billetterie | 1 actif | Illimité |
| Tickets par événement | 100 (gratuits uniquement) | Illimité (payants OK) |
| Scan QR caméra | Non | Oui |
| API v1 — pagination | Max 100 résultats par page | |
| Webhooks | 3 retries, backoff exponentiel | |
Codes d'erreur
| Code | Signification | Solution |
|---|---|---|
| 401 | Unauthorized | Clé API manquante, invalide ou expirée |
| 403 | Insufficient scope | La clé API n'a pas le scope requis pour cette action |
| 404 | Not found | La ressource demandée n'existe pas ou n'appartient pas à votre projet |
| 429 | Rate limited | Trop de requêtes — attendez avant de réessayer |
| 500 | Server error | Erreur interne — contactez le support si persistant |
{
"error": "Insufficient scope",
"message": "La clé API nécessite le scope 'forms:read' pour cette action"
}Besoin d'aide ? Contactez-nous à assistance@sabi.tools
Conforme à la loi n° 2024/017 — SABI Workspace — Improved Agency, Douala, Cameroun