Reliez Dawwerni à votre caisse, votre site, votre CRM ou vos outils d’automatisation : points de fidélité attribués à chaque vente, clients synchronisés, cadeaux vérifiés au comptoir et événements envoyés en temps réel à votre serveur.
Trois étapes pour votre premier appel. Comptez cinq minutes.
1
Activez les intégrations
Les intégrations (API, webhooks, caisse) sont incluses dans les offres Premium et Entreprise. Le propriétaire du compte gère les clés.
2
Créez une clé API
Donnez-lui un nom, choisissez les permissions et, si besoin, un seul commerce. La clé complète (dw_live_…) n’est affichée qu’une fois : stockez-la côté serveur.
3
Faites votre premier appel
GET /me renvoie le nom de la clé, ses permissions et les commerces accessibles.
Dans l’applicationMenu→Intégrations→Clés API→Créer une clé
Chaque requête porte votre clé API dans l’en-tête Authorization (Bearer) ou X-Api-Key. Tout passe en HTTPS, en JSON.
HTTP headers
Authorization: Bearer dw_live_a1b2c3d4••••••••••••••••••••••••••••••••••••••••
# or
X-Api-Key: dw_live_a1b2c3d4••••••••••••••••••••••••••••••••••••••••
Utilisez la clé uniquement sur vos serveurs : l’API n’accepte pas les appels depuis un navigateur (CORS) et une clé publiée dans un site ou une app mobile serait exposée.
Une clé n’agit que dans ses permissions et sur ses commerces ; sinon l’API répond 403.
Si la clé couvre plusieurs commerces, envoyez businessId dans la requête (sinon 400 BUSINESS_REQUIRED).
Une clé révoquée cesse de fonctionner immédiatement (401 API_KEY_REVOKED). 20 clés actives maximum par compte.
Permissions (scopes)
Permission
Autorise
customers:read
Chercher des clients et lire leurs points
customers:write
Créer ou mettre à jour des clients
points:write
Enregistrer des ventes et attribuer des points
rewards:read
Lister les récompenses de fidélité
redemptions:write
Échanger des points contre une récompense
winnings:verify
Vérifier et remettre les cadeaux gagnés
webhooks:manage
Gérer les endpoints webhook
Conventions
URL de base
https://app.dawwerni.com/api/ext/v1 — JSON uniquement (Content-Type: application/json pour les POST).
Dates
Format ISO 8601. Les champs de compte client (createdAt, updatedAt) sont en heure locale sans fuseau ; les autres (clés, webhooks, journaux) sont en UTC avec « Z ».
Téléphones
Format international conseillé (+21698123456). « 00216… » et les espaces sont acceptés ; un client est reconnu par e-mail puis par téléphone.
Pagination
Les listes acceptent page (à partir de 0) et size (20 par défaut, 100 maximum) et renvoient data + page.
Idempotence
Ajoutez un en-tête Idempotency-Key (un UUID) à chaque POST. Le même appel rejoué sous 24 h renvoie la réponse d’origine avec Idempotent-Replayed: true, sans refaire l’opération.
Limite de débit
120 requêtes par minute et par clé. Au-delà : HTTP 429 avec un en-tête Retry-After.
Versions
La v1 ne reçoit que des ajouts (nouveaux champs, nouveaux endpoints). Ignorez les champs inconnus dans les réponses.
Enregistre un ticket de caisse et crédite les points selon les règles de votre programme de fidélité. Le client est créé s’il n’existe pas.
externalId identifie le ticket : renvoyer le même externalId renvoie le premier résultat avec "duplicate": true et n’attribue rien.
Sans "points", le montant est converti par la règle de votre carte de fidélité (points par unité dépensée). Activez la carte de fidélité dans Fidélité ; sinon pointsAwarded vaut 0.
Les remboursements ne retirent pas de points : n’envoyez que les ventes.
Paramètres
Nom
Où
Type
Description
externalId*
body
string ≤ 120
Id unique du ticket dans votre caisse (obligatoire)
amount*
body
number ≥ 0
Montant payé (obligatoire)
customer*
body
{ id | phone | email, name }
Client : id Dawwerni, ou téléphone / e-mail (+ nom) (obligatoire)
currency
body
ISO 4217 (TND, EGP, SAR…)
Devise ISO (TND, EGP, SAR, USD…) (optionnel)
points
body
integer 0–1 000 000
Points imposés (sinon calculés par votre règle) (optionnel)
items
body
[{ sku, name, quantity, unitPrice }]
Lignes du ticket (seul leur nombre est enregistré) (optionnel)
businessId
body
integer
Obligatoire si la clé couvre plusieurs commerces (optionnel)
422 Unprocessable Entity RéponseRéponse réelle de l’API (données de test)
{
"error": {
"code": "NO_PARTICIPATION_FOR_CUSTOMER",
"message": "This customer has no participation yet; redemptions through the API need at least one"
}
}
Vérifier un cadeau gagné
POST/api/ext/v1/winnings/verify
Permission: winnings:verify
Comme le scan du QR dans l’application : contrôle le code, marque le cadeau comme remis et renvoie le gagnant.
code accepte le jeton du QR, l’URL complète /verify-winning/… ou la référence de 8 caractères de l’e-mail du gagnant.
Un cadeau déjà remis renvoie 200 avec "verified": false et "alreadyClaimed": true.
Paramètres
Nom
Où
Type
Description
code*
body
string
Jeton QR, URL de vérification ou référence à 8 caractères (obligatoire)
Enregistre une URL HTTPS publique et les événements à recevoir. La réponse contient le secret de signature (aussi visible plus tard dans l’application).
HTTPS obligatoire ; les adresses privées ou locales sont refusées. 10 endpoints maximum par compte.
Paramètres
Nom
Où
Type
Description
url*
body
https URL
URL HTTPS publique de votre serveur (obligatoire)
events*
body
string[]
Événements à recevoir (voir Webhooks) (obligatoire)
204 No Content RéponseRéponse réelle de l’API (données de test)
Codes d’erreur
Toutes les erreurs ont la même forme. Fiez-vous au code (stable) ; le message, en anglais, peut évoluer.
403 ForbiddenRéponse réelle de l’API (données de test)
{
"error": {
"code": "INSUFFICIENT_SCOPE",
"message": "This API key is missing the scope points:write"
}
}
HTTP
Code
Signification
400
VALIDATION_FAILED
Paramètre manquant ou mal formé
400
INVALID_JSON
Le corps n’est pas un JSON valide
400
CUSTOMER_IDENTIFIER_REQUIRED
Téléphone ou e-mail requis
400
INVALID_PHONE
Téléphone invalide (format international conseillé)
400
INVALID_EMAIL
E-mail invalide
400
EXTERNAL_ID_REQUIRED
externalId manquant (120 caractères max.)
400
INVALID_AMOUNT
Montant manquant ou négatif
400
INVALID_CURRENCY
Devise hors format ISO 4217
400
INVALID_POINTS
points hors de 0 à 1 000 000
400
BUSINESS_REQUIRED
La clé couvre plusieurs commerces : envoyez businessId
400
INVALID_IDEMPOTENCY_KEY
Idempotency-Key : 1 à 255 caractères ASCII
400
INVALID_EXTERNAL_ID
externalId trop long
400
CODE_REQUIRED
code manquant
400
INVALID_CODE
Ce n’est pas un code cadeau Dawwerni
400
INVALID_WEBHOOK_URL
URL refusée (HTTPS public uniquement)
400
EVENTS_REQUIRED
Choisissez au moins un événement
400
UNKNOWN_EVENT
Événement inconnu
401
UNAUTHENTICATED
Aucune clé API envoyée
401
INVALID_API_KEY
Clé API invalide
401
API_KEY_REVOKED
Clé API révoquée
403
ACCOUNT_INACTIVE
Compte Dawwerni inactif
403
FEATURE_NOT_ENABLED
Intégrations non incluses dans l’offre
403
INSUFFICIENT_SCOPE
Permission manquante sur la clé
403
BUSINESS_FORBIDDEN
Commerce hors de portée de la clé
403
BUSINESS_UNAVAILABLE
Le commerce de la clé a été supprimé
404
CUSTOMER_NOT_FOUND
Client introuvable
404
REWARD_NOT_FOUND
Récompense introuvable
404
WINNING_NOT_FOUND
Aucun cadeau pour ce code
404
WEBHOOK_NOT_FOUND
Endpoint webhook introuvable
409
WINNING_NOT_CLAIMABLE
Participation sans cadeau (perdue ou annulée)
409
IDEMPOTENCY_CONFLICT
Même Idempotency-Key en cours de traitement : réessayez
409
DUPLICATE_EXTERNAL_ID
Même externalId en cours de traitement : réessayez
415
UNSUPPORTED_MEDIA_TYPE
Envoyez Content-Type: application/json
422
IDEMPOTENCY_KEY_REUSED
Idempotency-Key déjà utilisée pour une autre requête
422
INSUFFICIENT_POINTS
Points insuffisants
422
REWARD_INACTIVE
Récompense désactivée
422
REWARD_OUT_OF_STOCK
Récompense en rupture
422
NO_PARTICIPATION_FOR_CUSTOMER
Le client n’a encore joué à aucun jeu
422
REDEMPTION_REJECTED
Échange refusé par les règles de fidélité
422
WEBHOOK_LIMIT_REACHED
10 endpoints maximum
422
NO_BUSINESS
Le compte n’a aucun commerce actif
429
RATE_LIMITED
Trop de requêtes : attendez Retry-After secondes
500
INTERNAL_ERROR
Erreur inattendue : réessayez plus tard
Webhooks
Dawwerni envoie un POST HTTPS signé à votre serveur à chaque événement choisi. Répondez par un statut 2xx en moins de 5 secondes. Compatible avec tout outil qui reçoit des webhooks (votre serveur, Make, n8n, Zapier…).
Dans l’applicationMenu→Intégrations→Webhooks→Ajouter un endpoint
Événements
Événement
Envoyé quand
participant.created
Un joueur laisse ses coordonnées dans un jeu (gagnant ou non)
winning.created
Un joueur gagne un cadeau
winning.claimed
Un cadeau est remis (scan du QR, changement de statut ou /winnings/verify)
points.earned
Des points sont crédités par une vente reçue de l’API ou d’une caisse connectée
Exemple construit à partir du code de l’API (nécessite une vraie participation à un jeu)
En-têtes et signature
HTTP
POST https://crm.example.com/webhooks/dawwerni
Content-Type: application/json; charset=utf-8
User-Agent: Dawwerni-Webhooks/1.0
X-Dawwerni-Event: points.earned
X-Dawwerni-Delivery: dlv_4k9x2m7q1w8z5r3t6y0u2p4s (same value on every retry)
X-Dawwerni-Timestamp: 1791927400 (unix seconds)
X-Dawwerni-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + "." + raw body)>
Vérifier la signature
Calculez HMAC-SHA256(secret, timestamp + "." + corps brut) en hexadécimal, préfixé par « sha256= », et comparez-le en temps constant à X-Dawwerni-Signature. Refusez les horodatages de plus de 5 minutes. Le bouton « Envoyer un test » de l’application envoie un événement ping.
// Node.js (Express): verify on the RAW body, not on re-serialised JSON
const crypto = require('crypto');
const express = require('express');
const app = express();
app.post('/webhooks/dawwerni', express.raw({ type: 'application/json' }), (req, res) => {
const ts = req.header('X-Dawwerni-Timestamp');
const sig = req.header('X-Dawwerni-Signature') || '';
if (!ts || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.sendStatus(400);
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.DAWWERNI_WEBHOOK_SECRET)
.update(ts + '.' + req.body.toString('utf8'))
.digest('hex');
const ok = sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
if (!ok) return res.sendStatus(401);
const event = JSON.parse(req.body.toString('utf8'));
// de-duplicate on req.header('X-Dawwerni-Delivery'), then handle event.type
res.sendStatus(200); // answer within 5 seconds, do slow work afterwards
});
Nouvelles tentatives
Échec = statut hors 2xx, délai dépassé ou erreur réseau. Nouvelles tentatives après 1 min, 5 min, 30 min, 2 h puis 12 h.
Après 20 échecs consécutifs, l’endpoint est désactivé et vous êtes notifié dans l’application. Réactivez-le une fois votre serveur réparé.
Un événement peut arriver deux fois ou dans le désordre : dédoublonnez sur X-Dawwerni-Delivery.
Journal des livraisons (30 jours) et rotation du secret disponibles dans l’application.
Connexion caisse (POS)
Pas de code à écrire : chaque connexion a un lien secret à coller dans votre caisse. Chaque vente avec le téléphone ou l’e-mail du client crédite ses points ; les tickets en double et les remboursements sont ignorés.
Dans l’applicationMenu→Intégrations→Connexions caisse→Connecter
FoodicsFOODICSRestaurants et cafés en Arabie saoudite et dans le Golfe : les commandes clôturées avec un client.
LoyverseLOYVERSECaisse gratuite pour cafés et boutiques : les tickets de vente avec un client (webhook « Receipts »).
Autre caisse (JSON)GENERIC_JSONToute caisse, e-shop ou outil d’automatisation capable d’envoyer une requête JSON après une vente.
Le lien de connexion
Le lien contient un jeton secret : quiconque le possède peut envoyer des ventes à votre compte. Remplacez-le à tout moment depuis l’application (l’ancien cesse immédiatement de fonctionner).
URL
https://app.dawwerni.com/api/ext/v1/pos/{CONNECTOR}/{connectionId}?token=pos_<secret token>
# CONNECTOR = GENERIC_JSON | LOYVERSE | FOODICS
# the token can also be sent as the header X-Dawwerni-Token: pos_<secret token>
Format JSON générique
Pour « Autre caisse », envoyez ce JSON après chaque vente. Champs reconnus :
externalId
Id unique du ticket (obligatoire)
amount
Total payé (obligatoire)
currency
Devise ISO, optionnelle
type
"sale" (par défaut) ; "refund" est ignoré
customer
phone, email, name : téléphone ou e-mail requis pour gagner des points
Envoyez {"purchases": [...]} (100 tickets maximum). La réponse compte les tickets traités, en double, ignorés et en échec ; le détail est dans « Derniers événements ».
Si vous définissez un secret de signature sur la connexion, chaque requête doit porter X-Dawwerni-Timestamp et X-Dawwerni-Signature (même schéma que les webhooks sortants, tolérance 5 minutes).
Node.js
// Optional: sign the request when a signing secret is set on the connection
const crypto = require('crypto');
const body = JSON.stringify(sale);
const ts = Math.floor(Date.now() / 1000).toString();
const signature = 'sha256=' + crypto.createHmac('sha256', process.env.DAWWERNI_POS_SIGNING_SECRET)
.update(ts + '.' + body).digest('hex');
await fetch(process.env.DAWWERNI_POS_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Dawwerni-Timestamp': ts, 'X-Dawwerni-Signature': signature },
body
});
Erreurs du lien caisse
400
INVALID_JSON
Le corps n’est pas un JSON valide
400
PAYLOAD_TOO_LARGE
Corps limité à 512 Ko
401
INVALID_CONNECTION_TOKEN
Connexion inconnue ou jeton invalide
401
INVALID_SIGNATURE
Signature absente, périmée ou fausse
403
CONNECTION_PAUSED
Connexion en pause dans Dawwerni
403
FEATURE_NOT_ENABLED
Intégrations non incluses dans l’offre
404
UNKNOWN_CONNECTOR
Type de caisse inconnu dans l’URL
422
UNSUPPORTED_PAYLOAD
Contenu illisible
429
RATE_LIMITED
120 événements par minute et par connexion
Widget & intégrations web
Affichez vos jeux et formulaires sur votre propre site ou boutique en ligne, sans API ni développeur.
Widget de jeu (widget.js)
Choisissez un jeu, un mode (bouton flottant, pop-up ou dans la page) et les sites autorisés ; l’application vous donne le code à coller.
Dans l’applicationMenu→Campagnes→Site web et boutique en ligne→Nouveau widget
HTML
<!-- Floating button or pop-up: paste before </body> -->
<script src="https://app.dawwerni.com/widget.js" data-dawwerni-key="dw_YOUR_WIDGET_KEY" async></script>
Le jeu s’ouvre dans une fenêtre isolée (Shadow DOM + iframe) : le style de votre site ne le casse pas, et inversement.
Pop-up déclenché immédiatement, après un délai, au défilement ou à l’intention de sortie, avec une fréquence d’affichage.
Sites autorisés : le widget ne s’affiche que sur vos domaines (et leurs sous-domaines).
Langue du jeu : attribut data-lang (ar, fr, en), sinon la langue de votre page.
Affichages et ouvertures comptés dans l’application. Le widget est activé sur demande : contactez-nous.
Mode « dans une page »
HTML
<!-- Inline: the game appears inside this block -->
<div data-dawwerni-game="dw_YOUR_WIDGET_KEY"></div>
<script src="https://app.dawwerni.com/widget.js" data-dawwerni-key="dw_YOUR_WIDGET_KEY" data-lang="ar" async></script>
Formulaire de contact intégré
Un formulaire Dawwerni publié peut être intégré dans une iframe ; il envoie sa hauteur pour s’adapter sans barre de défilement.
Dans l’applicationMenu→Prospects→Formulaires→Partager→Intégrer sur votre site
HTML
<iframe id="dw-lead-your-form" src="https://app.dawwerni.com/f/your-form?embed=1&src=website"
title="Contact form" style="width:100%;max-width:600px;min-height:640px;border:0" loading="lazy"></iframe>
<script>
// optional: the form reports its height so the frame never shows a scrollbar
window.addEventListener('message', function (e) {
if (e.origin !== 'https://app.dawwerni.com') return;
var d = e.data || {};
if (d.type === 'dawwerni-lead-form:height' && d.slug === 'your-form') {
document.getElementById('dw-lead-your-form').style.height = (d.height + 8) + 'px';
}
});
</script>
Liens publics & QR codes
Chaque module publie une page publique. L’application vous donne le lien exact (et un QR code à imprimer pour le menu, le lien bio et les formulaires) ; voici les formats.
Ces pages n’ont pas besoin de clé API. Les liens de jeux sont générés par l’application : ne les construisez pas vous-même.
Besoin d’aide pour intégrer ?
Une caisse non listée, un ERP, une intégration sur mesure (offre Entreprise) ? Écrivez-nous avec le nom de votre outil : nous vous répondons avec la meilleure option.