Intégrez WhatsApp dans votre SaaS sans reconstruire toute la plomberie technique
L'API Partner SMSV permet aux éditeurs SaaS, CRM, ERP et marketplaces d'offrir un vrai module WhatsApp en marque blanche. Chaque client connecte son propre numéro, vous gardez l'expérience dans votre produit, et SMSV gère l'infrastructure d'envoi, les webhooks et l'isolation multi-tenant.
Pour tester rapidement, créez un compte partenaire puis suivez le quick start pour connecter un premier client.
Multi-tenant
Une Partner Key pour piloter plusieurs clients, avec séparation claire des organisations et des numéros.
Prêt pour les devs
SDKs JS, Python et PHP, exemples curl et parcours d'intégration bout en bout.
Cloud ou local
Mode Cloud si vous voulez aller vite, Connector si vous voulez davantage de contrôle d'hébergement.
Architecture
Le Partner API repose sur deux couches simples pour garder une gouvernance multi-tenant propre :
Couche 1 — Partner Key
X-Partner-Key: pk_cloud_xxx
Gestion des senders (créer, QR, statut, supprimer) et envoi de messages via Partner Cloud.
Couche 2 — App API Key
X-API-Key: sp_live_xxx
Chaque app créée via Partner reçoit sa propre API key pour contacts, campagnes, templates, batch.
Partner API → SMSV Cloud → WhatsApp → Client finalAuthentification
Partner Key (gestion multi-tenant)
Utilisée pour créer/gérer les senders WhatsApp de vos clients et envoyer des messages via le Partner Cloud.
curl -H "X-Partner-Key: pk_cloud_xxxxx" \
https://smsv.tech/api/v1/partner/cloud/sendersObtenez votre Partner Key dans Dashboard → Partenaire → Vue d'ensemble.
App API Key (par client)
Chaque app créée via Partner reçoit une API key sp_live_xxx. Utilisez-la pour les contacts, campagnes, templates et envois directs.
curl -H "X-API-Key: sp_live_xxxxx" \
https://smsv.tech/api/v1/text \
-d '{"to": "+22370000000", "text": "Bonjour !"}'Quick Start
Voici le chemin minimal pour afficher le QR dans votre interface, connecter le numéro du client, puis commencer les envois depuis votre application.
Activer le mode Partner
Dashboard → Paramètres → Partenaire → Activer. Vous recevez votre pk_cloud_xxx.
Créer un sender pour votre client
POST /v1/partner/cloud/senders avec l'ID unique de votre client (externalOrgId).
Afficher le QR code
GET /v1/partner/cloud/senders/{orgId}/qr — affichez le QR dans votre UI pour que votre client scanne.
Attendre la connexion
Poll GET /v1/partner/cloud/senders/{orgId}/status toutes les 3s jusqu'à status = 'ready'.
Envoyer des messages
POST /v1/partner/cloud/messages/send — votre client envoie des messages WhatsApp depuis votre app.
# 1. Créer un sender
curl -X POST "https://smsv.tech/api/v1/partner/cloud/senders" \
-H "X-Partner-Key: pk_cloud_xxxxx" \
-H "Content-Type: application/json" \
-d '{"externalOrgId": "client-123", "displayName": "Boutique Awa"}'
# 2. Reconnecter pour démarrer la session
curl -X POST "https://smsv.tech/api/v1/partner/cloud/senders/client-123/reconnect" \
-H "X-Partner-Key: pk_cloud_xxxxx"
# 3. Récupérer le QR code (attendre 2s après reconnect)
curl "https://smsv.tech/api/v1/partner/cloud/senders/client-123/qr" \
-H "X-Partner-Key: pk_cloud_xxxxx"
# 4. Vérifier le statut (poll toutes les 3s)
curl "https://smsv.tech/api/v1/partner/cloud/senders/client-123/status" \
-H "X-Partner-Key: pk_cloud_xxxxx"
# 5. Envoyer un message (quand status = ready)
curl -X POST "https://smsv.tech/api/v1/partner/cloud/messages/send" \
-H "X-Partner-Key: pk_cloud_xxxxx" \
-H "Content-Type: application/json" \
-d '{"externalOrgId": "client-123", "to": "+22370000000", "text": "Bonjour !"}'Objectif recommandé pour votre première intégration
Ouvrez un compte partenaire, connectez un premier sender de test, affichez le QR dans votre UI, puis validez un envoi de message de bout en bout avant de passer en production.
Partner Cloud API
Base URL : https://smsv.tech/api/v1/partner/cloud
Auth : X-Partner-Key: pk_cloud_xxx
Gestion des Senders
Un "sender" est une session WhatsApp liée à un de vos clients. Chaque client est identifié par son externalOrgId (un ID unique dans votre système).
/v1/partner/cloud/sendersLister tous les senders. Filtrer par ownerExternalOrgId en query param.
Response
{
"success": true,
"data": [
{
"externalOrgId": "client-123",
"status": "ready",
"phoneNumber": "+22370000000",
"displayName": "Boutique Awa",
"connectedAt": "2026-03-10T12:00:00Z"
}
]
}/v1/partner/cloud/sendersCréer un sender WhatsApp pour un client.
Request Body
{
"externalOrgId": "client-123",
"displayName": "Boutique Awa"
}Response
{
"success": true,
"data": {
"externalOrgId": "client-123",
"senderId": "sender_abc123",
"status": "INITIALIZING"
}
}/v1/partner/cloud/senders/{orgId}Récupérer les détails d'un sender par son externalOrgId.
Response
{
"success": true,
"data": {
"externalOrgId": "client-123",
"status": "ready",
"phoneNumber": "+22370000000",
"connectedAt": "2026-03-10T12:00:00Z"
}
}/v1/partner/cloud/senders/{orgId}/qrRécupérer le QR code WhatsApp à afficher dans votre UI.
Response
{
"success": true,
"data": {
"status": "qr_ready",
"qrCode": "data:image/png;base64,iVBOR...",
"expiresIn": 60
}
}/v1/partner/cloud/senders/{orgId}/statusVérifier le statut de connexion. Poll toutes les 3s jusqu'à 'ready' ou 'connected'.
Response
{
"success": true,
"data": {
"externalOrgId": "client-123",
"status": "ready",
"phoneNumber": "+22370000000",
"connectedAt": "2026-03-10T12:00:00Z"
}
}/v1/partner/cloud/senders/{orgId}/reconnectReconnecter un sender déconnecté. Nécessaire avant de récupérer un nouveau QR.
Response
{ "success": true }/v1/partner/cloud/senders/{orgId}Supprimer un sender et déconnecter la session WhatsApp.
Response
{ "success": true }Référence des statuts
not_initializedPas de session activeAppeler /reconnectinitializingSession en cours de démarrageAttendre + pollqr_readyQR code disponibleAfficher QR + poll /statusconnectingQR scanné, connexion en coursAttendre + pollreadyConnecté et prêt à envoyerEnvoyer des messagesconnectedAlias de readyEnvoyer des messagesdisconnectedSession perdueAppeler /reconnectMessagerie (Partner Cloud)
/v1/partner/cloud/messages/sendEnvoyer un message texte via le sender d'un client.
Request Body
{
"externalOrgId": "client-123",
"to": "+22370000000",
"text": "Bonjour depuis votre app !"
}Response
{
"success": true,
"messageId": "msg_abc123",
"status": "queued"
}/v1/partner/cloud/messages/send-documentEnvoyer un document (PDF, Excel, etc.).
Request Body
{
"externalOrgId": "client-123",
"to": "+22370000000",
"documentUrl": "https://example.com/facture.pdf",
"filename": "Facture-001.pdf",
"caption": "Votre facture"
}Response
{
"success": true,
"messageId": "msg_def456",
"status": "queued"
}/v1/partner/cloud/messages/send-imageEnvoyer une image avec légende optionnelle.
Request Body
{
"externalOrgId": "client-123",
"to": "+22370000000",
"imageUrl": "https://example.com/photo.jpg",
"caption": "Photo du produit"
}Response
{
"success": true,
"messageId": "msg_ghi789",
"status": "queued"
}/v1/partner/cloud/agent/chatAgent IA conversationnel avec catalogue produit.
Request Body
{
"externalOrgId": "client-123",
"customerPhone": "+22370000000",
"message": "Je cherche un produit",
"products": [
{ "id": "p1", "name": "T-shirt", "price": 5000 }
]
}Response
{
"success": true,
"response": "Nous avons un T-shirt à 5 000 FCFA.",
"actions": ["product_search"]
}App API (par client)
Chaque app créée via le Partner API reçoit sa propre X-API-Key (sp_live_xxx). Utilisez-la pour accéder aux endpoints ci-dessous, scoped à cette app.
Base URL : https://smsv.tech/api
Auth : X-API-Key: sp_live_xxx
/v1/meInformations sur l'app : plan, limites, senders actifs.
Response
{
"id": "app_xxx",
"name": "Boutique Awa",
"plan": "STARTER",
"limits": {
"messagesPerMonth": 2000,
"accounts": 2
},
"senders": [
{ "id": "s1", "phoneNumber": "+22370000000", "status": "ready" }
]
}Messagerie (App API)
Endpoints d'envoi direct, utilisables avec la clé API de chaque app.
/v1/textEnvoyer un message texte.
Request Body
{
"to": "+22370000000",
"text": "Bonjour !",
"senderId": "optional-sender-id"
}Response
{
"success": true,
"messageId": "msg_xxx",
"status": "queued"
}/v1/imageEnvoyer une image.
Request Body
{
"to": "+22370000000",
"imageUrl": "https://example.com/photo.jpg",
"caption": "Photo"
}Response
{ "success": true, "messageId": "msg_xxx" }/v1/documentEnvoyer un document.
Request Body
{
"to": "+22370000000",
"documentUrl": "https://example.com/file.pdf",
"filename": "file.pdf"
}Response
{ "success": true, "messageId": "msg_xxx" }/v1/templateEnvoyer un message template (Meta Cloud API).
Request Body
{
"to": "+22370000000",
"templateSlug": "order_confirmation",
"variables": { "name": "Awa", "order": "#1234" },
"language": "fr"
}Response
{ "success": true, "messageId": "msg_xxx" }/v1/audioEnvoyer un fichier audio.
Request Body
{
"to": "+22370000000",
"audioUrl": "https://example.com/audio.mp3"
}Response
{ "success": true, "messageId": "msg_xxx" }/v1/videoEnvoyer une vidéo.
Request Body
{
"to": "+22370000000",
"videoUrl": "https://example.com/video.mp4",
"caption": "Vidéo promo"
}Response
{ "success": true, "messageId": "msg_xxx" }/v1/locationEnvoyer une localisation GPS.
Request Body
{
"to": "+22370000000",
"latitude": 12.6392,
"longitude": -8.0029,
"name": "Bamako, Mali"
}Response
{ "success": true, "messageId": "msg_xxx" }Contacts
/v1/contactsLister les contacts. Supporte pagination (?page=1&limit=50) et recherche (?search=awa).
Response
{
"contacts": [
{ "id": "c1", "phone": "+22370000000", "name": "Awa", "tags": ["vip"] }
],
"total": 150,
"page": 1,
"limit": 50
}/v1/contactsCréer un contact.
Request Body
{
"phone": "+22370000000",
"name": "Awa Diallo",
"email": "awa@example.com",
"tags": ["client", "vip"]
}Response
{
"id": "c_xxx",
"phone": "+22370000000",
"name": "Awa Diallo"
}/v1/contacts/{id}Récupérer un contact par ID.
Response
{
"id": "c_xxx",
"phone": "+22370000000",
"name": "Awa Diallo",
"tags": ["client", "vip"],
"createdAt": "2026-01-15T10:00:00Z"
}/v1/contacts/{id}Mettre à jour un contact.
Request Body
{
"name": "Awa Diallo-Traoré",
"tags": ["client", "vip", "premium"]
}Response
{ "id": "c_xxx", "name": "Awa Diallo-Traoré" }/v1/contacts/{id}Supprimer un contact.
Response
{ "success": true }/v1/contacts/importImporter des contacts en masse (CSV ou JSON).
Request Body
{
"contacts": [
{ "phone": "+22370000001", "name": "Contact 1" },
{ "phone": "+22370000002", "name": "Contact 2" }
]
}Response
{
"imported": 2,
"duplicates": 0,
"errors": 0
}Campagnes
/v1/campaignsLister les campagnes. Filtrer par statut (?status=DRAFT).
Response
{
"campaigns": [
{
"id": "camp_xxx",
"name": "Promo Ramadan",
"status": "COMPLETED",
"audienceCount": 500,
"sentCount": 498
}
],
"total": 12
}/v1/campaignsCréer une campagne.
Request Body
{
"name": "Promo Ramadan",
"messageContent": "Profitez de -20% !",
"segmentTags": ["client"],
"scheduledAt": "2026-03-15T08:00:00Z"
}Response
{
"id": "camp_xxx",
"name": "Promo Ramadan",
"status": "DRAFT"
}/v1/campaigns/{id}/sendLancer l'envoi d'une campagne.
Response
{ "success": true, "status": "RUNNING" }/v1/campaigns/{id}/pauseMettre en pause une campagne en cours.
Response
{ "success": true, "status": "PAUSED" }/v1/campaigns/{id}/resumeReprendre une campagne en pause.
Response
{ "success": true, "status": "RUNNING" }/v1/campaigns/{id}/statsStatistiques d'une campagne.
Response
{
"audienceCount": 500,
"sentCount": 498,
"deliveredCount": 490,
"readCount": 320,
"failedCount": 2
}Templates
/api/templatesLister les templates de l'app.
Response
[
{
"id": "t_xxx",
"slug": "order_confirmation",
"name": "Confirmation de commande",
"content": "Bonjour {{name}}, commande {{order}} confirmée.",
"variables": ["name", "order"]
}
]/api/templatesCréer un template.
Request Body
{
"name": "Confirmation de commande",
"slug": "order_confirmation",
"content": "Bonjour {{name}}, votre commande {{order}} est confirmée.",
"variables": ["name", "order"]
}Response
{
"id": "t_xxx",
"slug": "order_confirmation"
}/api/messages/templateEnvoyer un message basé sur un template.
Request Body
{
"to": "+22370000000",
"templateSlug": "order_confirmation",
"variables": { "name": "Awa", "order": "#1234" }
}Response
{ "success": true, "messageId": "msg_xxx" }Envoi en masse (Batch)
/v1/batchEnvoyer jusqu'à 1000 messages en un seul appel.
Request Body
{
"messages": [
{ "to": "+22370000001", "text": "Bonjour 1" },
{ "to": "+22370000002", "text": "Bonjour 2" }
]
}Response
{
"batchId": "batch_xxx",
"total": 2,
"queued": 2
}/v1/batch/{batchId}Vérifier le statut d'un batch.
Response
{
"batchId": "batch_xxx",
"total": 2,
"sent": 2,
"failed": 0,
"status": "completed"
}Média
/v1/media/uploadUploader un fichier (image, document, audio, vidéo). Retourne une URL utilisable dans les endpoints d'envoi.
Request Body
Content-Type: multipart/form-data file: <binary>
Response
{
"url": "https://smsv.tech/uploads/xxx.pdf",
"mimeType": "application/pdf",
"size": 125000
}Webhooks
Recevez des notifications en temps réel sur les événements de messagerie et de connexion.
Événements disponibles
message.sentMessage envoyémessage.deliveredMessage délivrémessage.readMessage lumessage.failedÉchec d'envoimessage.receivedNouveau message entrantsender.connectedWhatsApp connectésender.disconnectedWhatsApp déconnectéFormat du payload
{
"event": "message.delivered",
"timestamp": "2026-03-13T14:30:00Z",
"externalOrgId": "client-123",
"data": {
"messageId": "msg_abc123",
"to": "+22370000000",
"status": "delivered"
}
}Vérification de signature
Chaque webhook inclut un header X-Webhook-Signature (HMAC SHA-256).
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const expected = 'sha256=' +
crypto.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}Configuration webhook (Partner)
Configurez votre URL webhook dans Dashboard → Partenaire → Paramètres, ou via l'API pour chaque app :
/apps/{appId}/webhooksCréer un endpoint webhook pour une app.
Request Body
{
"url": "https://yourapp.com/webhooks/smsv",
"events": ["message.sent", "message.delivered", "message.received"],
"isActive": true
}Response
{
"id": "wh_xxx",
"url": "https://yourapp.com/webhooks/smsv",
"secret": "whsec_xxxxx",
"isActive": true
}SDKs
Tous les SDKs sont open-source et disponibles sur GitHub.
JavaScript / TypeScript
Client complet (Standard + Partner).
# Cloner le repo puis installer localement
git clone https://github.com/dione24/smsv-sdk.git
cd smsv-sdk/js && npm install && npm run build
# Puis dans votre projet :
npm install ../smsv-sdk/jsimport { SmsvPartnerClient } from '@sahelpay/smsv';
const client = new SmsvPartnerClient({
partnerKey: 'pk_cloud_xxxxx'
});
const sender = await client.createSender({
externalOrgId: 'client-123',
displayName: 'Boutique Awa'
});
const { qrCode } = await client.getQRCode('client-123');
await client.sendMessage({
externalOrgId: 'client-123',
to: '+22370000000',
text: 'Bonjour !'
});Python
Client Standard + Partner.
# Cloner le repo puis installer localement
git clone https://github.com/dione24/smsv-sdk.git
pip install ./smsv-sdk/pythonfrom smsv import SMSvPartnerClient
client = SMSvPartnerClient(
partner_key="pk_cloud_xxxxx"
)
sender = client.create_sender(
"client-123", "Boutique Awa"
)
qr = client.get_qr_code("client-123")
client.send_message(
"client-123",
"+22370000000",
"Bonjour !"
)PHP / Laravel
Client Standard + Partner.
# Cloner le repo, puis dans composer.json ajouter :
# "repositories": [{ "type": "path", "url": "./smsv-sdk/php-laravel" }]
git clone https://github.com/dione24/smsv-sdk.git
composer require sahelpay/smsv-laravel @devuse Sahelpay\Smsv\SmsvPartnerClient;
$client = new SmsvPartnerClient(
'pk_cloud_xxxxx'
);
$sender = $client->createSender(
'client-123', 'Boutique Awa'
);
$qr = $client->getQRCode('client-123');
$client->sendMessage(
'client-123',
'+22370000000',
'Bonjour !'
);Erreurs
Toutes les erreurs suivent le même format :
{
"statusCode": 400,
"message": "Description de l'erreur",
"code": "VALIDATION_ERROR"
}| Code HTTP | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR | Paramètres invalides |
| 401 | UNAUTHORIZED | Clé API manquante ou invalide |
| 403 | FORBIDDEN | Accès refusé (plan, quota, permissions) |
| 404 | NOT_FOUND | Ressource introuvable |
| 409 | CONFLICT | Ressource déjà existante (ex: sender avec même orgId) |
| 429 | RATE_LIMITED | Trop de requêtes. Voir header Retry-After |
| 500 | INTERNAL_ERROR | Erreur serveur. Réessayer avec backoff exponentiel |
Retry-After (en secondes), puis réessayez avec un backoff exponentiel (1s, 2s, 4s, max 60s). Les SDKs gèrent cela automatiquement.Limites & Quotas
| Ressource | Limite | Notes |
|---|---|---|
| Requêtes API | 100/min par clé | Header X-RateLimit-Remaining |
| Messages/mois | Selon plan | Voir /v1/me pour les limites actuelles |
| Batch send | 1000 messages/appel | POST /v1/batch |
| Upload média | 16 MB/fichier | POST /v1/media/upload |
| Senders/partner | Selon plan partner | Visible dans Dashboard → Partenaire |
| Contacts/import | 10 000/appel | POST /v1/contacts/import |
Exemple complet : SaaS de facturation
Scénario : votre SaaS de facturation permet à chaque client de lier son WhatsApp pour envoyer automatiquement les factures PDF à ses propres clients.
1. Onboarding : le client lie son WhatsApp
import { SmsvPartnerClient } from '@sahelpay/smsv';
const smsv = new SmsvPartnerClient({
partnerKey: process.env.SMSV_PARTNER_KEY!
});
// Quand le client clique "Connecter WhatsApp"
app.post('/api/whatsapp/connect', async (req, res) => {
const { tenantId } = req.auth;
// Créer ou récupérer le sender
let sender;
try {
sender = await smsv.getSender(tenantId);
} catch {
sender = await smsv.createSender({
externalOrgId: tenantId,
displayName: req.auth.companyName
});
}
// Si déconnecté, reconnecter
if (sender.status === 'disconnected' || sender.status === 'not_initialized') {
await smsv.reconnectSender(tenantId);
}
// Récupérer le QR
const qr = await smsv.getQRCode(tenantId);
res.json({ qrCode: qr.qrCode, status: qr.status });
});
// Frontend poll ce endpoint
app.get('/api/whatsapp/status', async (req, res) => {
const { tenantId } = req.auth;
const status = await smsv.getStatus(tenantId);
res.json(status);
});2. Envoi automatique de facture
// Quand une facture est payée
async function onInvoicePaid(invoice: Invoice) {
const tenant = await db.tenant.findUnique({
where: { id: invoice.tenantId }
});
// Vérifier que le client a WhatsApp connecté
const status = await smsv.getStatus(tenant.id);
if (status.status !== 'ready' && status.status !== 'connected') {
console.log('WhatsApp non connecté pour', tenant.id);
return;
}
// Envoyer la facture PDF
await smsv.sendDocument({
externalOrgId: tenant.id,
to: invoice.customerPhone,
documentUrl: invoice.pdfUrl,
filename: `Facture-${invoice.number}.pdf`,
caption: `Facture ${invoice.number} — ${invoice.total} FCFA`
});
await db.invoice.update({
where: { id: invoice.id },
data: { whatsappSent: true }
});
}3. Recevoir les webhooks
app.post('/webhooks/smsv', async (req, res) => {
const signature = req.headers['x-webhook-signature'] as string;
const isValid = smsv.verifyWebhook(
JSON.stringify(req.body),
signature,
process.env.SMSV_WEBHOOK_SECRET!
);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
const { event, externalOrgId, data } = req.body;
switch (event) {
case 'message.delivered':
await db.invoice.updateMany({
where: { tenantId: externalOrgId, messageId: data.messageId },
data: { deliveryStatus: 'delivered' }
});
break;
case 'message.failed':
console.error('Message failed:', data);
break;
case 'sender.disconnected':
await notifyTenant(externalOrgId, 'WhatsApp déconnecté');
break;
}
res.json({ received: true });
});Même exemple en Python
from smsv import SMSvPartnerClient
smsv = SMSvPartnerClient(partner_key="pk_cloud_xxxxx")
# Onboarding
def connect_whatsapp(tenant_id: str, company_name: str):
try:
sender = smsv.get_sender(tenant_id)
except Exception:
sender = smsv.create_sender(tenant_id, company_name)
if sender["status"] in ("disconnected", "not_initialized"):
smsv.reconnect_sender(tenant_id)
qr = smsv.get_qr_code(tenant_id)
return {"qr_code": qr["qrCode"], "status": qr["status"]}
# Envoi de facture
def send_invoice(tenant_id: str, customer_phone: str, pdf_url: str, number: str):
status = smsv.get_status(tenant_id)
if status["status"] not in ("ready", "connected"):
return False
smsv.send_document(
org_id=tenant_id,
to=customer_phone,
document_url=pdf_url,
filename=f"Facture-{number}.pdf",
caption=f"Facture {number}"
)
return TrueBesoin d'aide ? support@smsv.tech · Page Partner · Créer un compte