Skip to main content

sage-messaging (Python)

SDK officiel de l'API Sage Messaging : envoi et lecture de messages WhatsApp et SMS, vérification des webhooks.

pip install sage-messaging

Python 3.9+. Client synchrone et asynchrone.

Démarrage

from sage_messaging import SageMessaging

client = SageMessaging(api_key="sk_...")  # ou variable SAGE_MESSAGING_API_KEY

# WhatsApp (canal par défaut)
client.messages.send(phone="+2250787572030", message="Bonjour !")

# SMS
client.sms.send(phone="+2250787572030", message="Votre code : 4821")

Asynchrone

from sage_messaging import AsyncSageMessaging

async with AsyncSageMessaging() as client:
    await client.messages.send(phone="+2250787572030", message="Bonjour !")

Les méthodes sont identiques ; il suffit de les await.

Configuration

Paramètre Variable d'environnement Défaut
api_key SAGE_MESSAGING_API_KEY —
base_url SAGE_MESSAGING_BASE_URL https://messaging.sagecoders.com
webhook_secret SAGE_MESSAGING_WEBHOOK_SECRET —
timeout 30 secondes
max_retries 2 (lectures uniquement)

Les lectures (GET) sont rejouées en cas d'erreur réseau, de 429 ou de 5xx. Les envois ne sont jamais rejoués : un envoi dont la réponse s'est perdue a pu partir, et le rejouer enverrait le message en double.

Envoi

# Message texte, WhatsApp ou SMS
client.messages.send(phone="+2250787572030", message="Salut", channel="sms")

# SMS différé, depuis un téléphone précis
from datetime import datetime, timedelta, timezone
client.sms.send(
    phone="+2250787572030",
    message="Rappel : RDV demain",
    from_="+2250555884378",                                   # voir client.sms.phones()
    send_at=datetime.now(timezone.utc) + timedelta(hours=2),  # 20 jours max
)

# Campagne SMS (1 à 100 destinataires)
client.sms.bulk_send(phones=["+2250787572030", "+2250555884378"], message="Promo -20 %")

# Médias WhatsApp (1 à 10, 16 Mo max chacun), envoyés dans l'ordre
res = client.messages.send_media(
    phone="+2250787572030",
    caption="Votre facture",
    media=[
        {"path": "facture.pdf"},                             # fichier local
        {"url": "https://exemple.com/photo.jpg"},            # URL publique
        {"data": image_bytes, "filename": "capture.png"},    # octets
    ],
)
if res["failed"]:  # succès partiel (HTTP 207)
    print("Échecs :", res["failed"])

Utilisez des datetime avec fuseau horaire pour send_at et since.

Lecture

client.conversations.list(unread_only=True, limit=20)
client.conversations.messages(42, direction="received")
client.conversations.mark_read(42)

client.messages.list(after_id=1000)  # flux global, id croissant

Les réponses paginées ont la forme {"data": [...], "meta": {"current_page", "per_page", "total", "last_page"}}.

Réponses citées

Quand un contact répond sur WhatsApp à un message précis, quoted_message_id donne l'id de ce message — le même que le message_id renvoyé par messages.send, ou sent[0]["message_id"] (la pièce qui porte la légende) pour messages.send_media. Il vaut None si ce n'est pas une citation ou si le message cité n'a pas été envoyé via SageMessaging.

Fichiers reçus (WhatsApp)

Un message reçu avec un fichier porte media: {"mimetype", "filename", "size"}. Le fichier n'est pas stocké : il est récupéré chez WhatsApp à la demande, et n'est disponible que tant que WhatsApp le conserve — téléchargez-le dès réception.

if msg.get("media"):
    file = client.messages.download_media(msg["id"])
    Path(file["filename"] or f"media-{msg['id']}").write_bytes(file["data"])

Erreurs : 404 sans fichier, 410 expiré chez WhatsApp, 413 au-delà de la limite (16 Mo, 100 Mo pour un document), 503 WhatsApp déconnecté.

Suivre les nouveaux messages sans webhook

for msg in client.messages.poll(direction="received", interval=5):
    print(msg["contact_phone"], msg["body"])

Sans after_id, seuls les messages arrivés après l'appel sont renvoyés. Pour reprendre après un redémarrage, conservez le dernier msg["id"] et passez-le en after_id.

Organisations clientes (multi-clients)

Chaque entreprise cliente devient une organisation de votre compte, avec son propre WhatsApp et son propre téléphone SMS. La clé API désigne l'organisation : les envois partent de ses canaux ou, tant qu'elle n'en a pas, de ceux de votre compte. Vos clients n'ont pas besoin de compte SageMessaging : ils scannent les QR codes depuis votre application.

# Clé du compte avec la permission organizations:manage
admin = SageMessaging()

# 1. Créer l'organisation : sa clé smsk-… n'est renvoyée qu'ici, conservez-la
created = admin.organizations.create(
    name="Hôtel Paradisias",
    abilities=["messages:send", "sms:send", "channels:manage"],
)

# 2. Avec la clé de l'organisation : QR codes à afficher à votre client
client = SageMessaging(api_key=created["api_key"])

wa = client.whatsapp.connect()
if wa["state"] == "connecting":
    show_qr(wa["qrcode"])  # data-URL PNG, expire en ~20 s : redemandez-le
# … jusqu'à ce que client.whatsapp.status()["connected"] soit vrai

sms = client.sms.provision()  # {"qrcode", "server_url"} pour l'application Android
# Plus tard : client.sms.qrcode() le réaffiche, client.sms.status() confirme le téléphone

# 3. Envoyer depuis le téléphone de l'organisation
client.messages.send(phone="+2250787572030", message="Bonjour !")

Gérer les organisations et leurs clés :

orgs = admin.organizations.list()  # la vôtre (is_default) en premier

org_id = created["organization"]["id"]
key = admin.organizations.keys.create(org_id, name="Back-office", abilities=["messages:send"])
# key["api_key"] : affichée une seule fois
admin.organizations.keys.list(org_id)  # jamais la clé elle-même
admin.organizations.keys.revoke(org_id, key["id"])

client.whatsapp.logout()  # déconnecte le téléphone de l'organisation

Une clé créée par API n'a jamais plus de permissions que la clé qui la crée, et ne reçoit jamais organizations:manage. Le client asynchrone expose les mêmes méthodes (await client.organizations.create(...)).

Webhooks

Chaque webhook est signé en HMAC-SHA256 (X-Signature, X-Timestamp). Passez le corps brut de la requête, pas un JSON re-sérialisé.

from sage_messaging import verify_webhook, WebhookVerificationError

# FastAPI
@app.post("/webhooks/sage")
async def sage_webhook(request: Request):
    try:
        event = verify_webhook(await request.body(), request.headers, SECRET)
    except WebhookVerificationError:
        raise HTTPException(400)

    if event["event"] == "message.received":
        msg = event["data"]["message"]
        contact = event["data"]["conversation"]
        ...
    return {"ok": True}

Django : verify_webhook(request.body, request.headers, SECRET) ; Flask : verify_webhook(request.get_data(), request.headers, SECRET).

Les requêtes de plus de 5 minutes sont refusées (tolerance=300) pour empêcher leur rejeu.

Erreurs

from sage_messaging import ValidationError, GatewayError, SageMessagingError

try:
    client.sms.send(phone="+2250787572030", message="...")
except ValidationError as e:     # 422 : champ invalide, canal non provisionné…
    print(e.errors, e.channel)
except GatewayError as e:        # 502 : la passerelle a refusé l'envoi
    print(e.detail)
except SageMessagingError as e:  # toutes les autres erreurs du SDK
    print(e)
Classe Cas
AuthenticationError 401 : clé absente ou invalide
PermissionDeniedError 403 : la clé n'a pas la permission requise
NotFoundError 404
ValidationError 422
RateLimitError 429
GatewayError 502 : refus de WhatsApp ou de la passerelle SMS
APIError classe mère des erreurs HTTP (.status, .body)
APIConnectionError réseau, DNS, délai dépassé
WebhookVerificationError signature absente, invalide ou expirée

Permissions des clés API

Permission Méthodes
messages:send messages.send, messages.send_media (WhatsApp)
sms:send sms.* et messages.send(channel="sms")
messages:read messages.list, messages.poll, conversations.*, sms.phones
channels:manage whatsapp.*, sms.provision, sms.qrcode, sms.status
organizations:manage organizations.*, organizations.keys.*

Metadata

Release files for sage-messaging 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sage-messaging 0.3.0
File Size Uploaded Provenance Actions
sage_messaging-0.3.0.tar.gz 17.8 kB Without provenance Details for sage_messaging-0.3.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for sage-messaging 0.3.0
File Interpreter ABI Platform Actions
sage_messaging-0.3.0-py3-none-any.whl Python 3 none any Details for sage_messaging-0.3.0-py3-none-any.whl

Total release size: 38.7 kB

Release files / sage_messaging-0.3.0.tar.gz

Download URL sage_messaging-0.3.0.tar.gz
Size 17.8 kB
Tags Source
SHA-256 checksum
How to use checksums
32de6c1ef11189b30fbeb290456f7eb1d0662e993bb5777084cf9254734f80e5
BLAKE2b-256 checksum
How to use checksums
2a809f0331097b70c4bbd97dc8bc6106c1fce59ff16f06447316b17dfa8aaf26
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.9

Release files / sage_messaging-0.3.0-py3-none-any.whl

Download URL sage_messaging-0.3.0-py3-none-any.whl
Size 20.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
973d7cc25b65386cd33747bf70aba1d1e1991dc0d3a3c9444ef7320344f2bd80
BLAKE2b-256 checksum
How to use checksums
a55c073fe8122dca6a6092764e2752ca3b69f48d6083070fa73863a7c02ceb8e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.9

Security for sage-messaging 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page