Développeurs

API & intégrations Dawwerni

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.

  • API REST JSON (v1)
  • Clés à permissions
  • Webhooks signés HMAC-SHA256
  • Offres Premium et Entreprise
Base URLhttps://app.dawwerni.com/api/ext/v1

Ce que vous pouvez construire

Six intégrations qui existent aujourd’hui, sans développement côté Dawwerni.

Démarrage rapide

Trois étapes pour votre premier appel. Comptez cinq minutes.

  1. 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. 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. 3
    Faites votre premier appel

    GET /me renvoie le nom de la clé, ses permissions et les commerces accessibles.

Dans l’applicationMenuIntégrationsClés APICréer une clé

Premier appel · cURL
curl https://app.dawwerni.com/api/ext/v1/me \
  -H "Authorization: Bearer $DAWWERNI_API_KEY"
200 OK RéponseRéponse réelle de l’API (données de test)
{
  "apiVersion": "v1",
  "key": {
    "id": 7,
    "name": "POS bridge - Tunis",
    "prefix": "dw_live_a1b2c3d4",
    "scopes": [
      "customers:read",
      "customers:write",
      "points:write",
      "redemptions:write",
      "rewards:read",
      "webhooks:manage",
      "winnings:verify"
    ],
    "businessScope": null
  },
  "businesses": [
    {
      "id": 186,
      "name": "Café Amira"
    }
  ],
  "rateLimitPerMinute": 120
}

Authentification

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)

PermissionAutorise
customers:readChercher des clients et lire leurs points
customers:writeCréer ou mettre à jour des clients
points:writeEnregistrer des ventes et attribuer des points
rewards:readLister les récompenses de fidélité
redemptions:writeÉchanger des points contre une récompense
winnings:verifyVérifier et remettre les cadeaux gagnés
webhooks:manageGé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.
Format des listes
{
  "data": [ … ],
  "page": { "number": 0, "size": 20, "totalElements": 42, "totalPages": 3, "hasNext": true }
}
En-têtes de limite
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 119
# when exceeded: HTTP 429 + Retry-After: <seconds>

Endpoints

Toutes les réponses ci-dessous ont été obtenues en appelant l’API sur un café de test à Tunis.

Informations de la clé

GET/api/ext/v1/me

Permission: toute clé valide

Nom, préfixe et permissions de la clé, commerces accessibles et limite de débit. Idéal pour tester votre configuration.

curl "https://app.dawwerni.com/api/ext/v1/me" \
  -H "Authorization: Bearer $DAWWERNI_API_KEY"
200 OK RéponseRéponse réelle de l’API (données de test)
{
  "apiVersion": "v1",
  "key": {
    "id": 7,
    "name": "POS bridge - Tunis",
    "prefix": "dw_live_a1b2c3d4",
    "scopes": [
      "customers:read",
      "customers:write",
      "points:write",
      "redemptions:write",
      "rewards:read",
      "webhooks:manage",
      "winnings:verify"
    ],
    "businessScope": null
  },
  "businesses": [
    {
      "id": 186,
      "name": "Café Amira"
    }
  ],
  "rateLimitPerMinute": 120
}

Chercher des clients

GET/api/ext/v1/customers

Permission: customers:read

Liste paginée des comptes fidélité. Filtrez par téléphone ou e-mail ; sans filtre, tous les clients des commerces de la clé.

Paramètres
NomOùTypeDescription
phonequerystringTéléphone du client (le + doit être encodé : %2B) (optionnel)
emailquerystringE-mail du client (optionnel)
businessIdqueryintegerLimiter à un commerce (optionnel)
pagequeryinteger ≥ 0Page, à partir de 0 (optionnel)
sizequeryinteger 1–100 (20)Taille de page (optionnel)
curl "https://app.dawwerni.com/api/ext/v1/customers?phone=%2B21698123456" \
  -H "Authorization: Bearer $DAWWERNI_API_KEY"
200 OK RéponseRéponse réelle de l’API (données de test)
{
  "data": [
    {
      "id": 313,
      "businessId": 186,
      "name": "Amira B.",
      "email": "[email protected]",
      "phone": "+21698123456",
      "points": {
        "spendable": 0,
        "locked": 0,
        "lifetimeEarned": 0
      },
      "createdAt": "2026-10-13T21:36:40.087382",
      "updatedAt": "2026-10-13T21:36:40.121926"
    }
  ],
  "page": {
    "number": 0,
    "size": 20,
    "totalElements": 1,
    "totalPages": 1,
    "hasNext": false
  }
}

Lire un client

GET/api/ext/v1/customers/313

Permission: customers:read

Un compte fidélité par son id Dawwerni.

Paramètres
NomOùTypeDescription
id*pathintegerId du client Dawwerni (obligatoire)
curl "https://app.dawwerni.com/api/ext/v1/customers/313" \
  -H "Authorization: Bearer $DAWWERNI_API_KEY"
200 OK RéponseRéponse réelle de l’API (données de test)
{
  "id": 313,
  "businessId": 186,
  "name": "Amira B.",
  "email": "[email protected]",
  "phone": "+21698123456",
  "points": {
    "spendable": 0,
    "locked": 0,
    "lifetimeEarned": 0
  },
  "createdAt": "2026-10-13T21:36:40.087382",
  "updatedAt": "2026-10-13T21:36:40.121926"
}
404 Not Found RéponseRéponse réelle de l’API (données de test)
{
  "error": {
    "code": "CUSTOMER_NOT_FOUND",
    "message": "Customer not found"
  }
}

Créer ou mettre à jour un client

POST/api/ext/v1/customers

Permission: customers:write

Retrouve le client par e-mail puis par téléphone ; le crée s’il n’existe pas (201) ou complète ses coordonnées (200).

Paramètres
NomOùTypeDescription
phonebodystring (E.164)Téléphone, téléphone ou e-mail obligatoire (optionnel)
emailbodystringE-mail, téléphone ou e-mail obligatoire (optionnel)
namebodystring ≤ 120Nom affiché (optionnel)
businessIdbodyintegerObligatoire si la clé couvre plusieurs commerces (optionnel)
Idempotency-Keyheaderstring ≤ 255Rejeu sans risque pendant 24 h (optionnel)
curl -X POST "https://app.dawwerni.com/api/ext/v1/customers" \
  -H "Authorization: Bearer $DAWWERNI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Amira Ben Salah", "phone": "+21698123456", "email": "[email protected]" }'
201 Created RéponseRéponse réelle de l’API (données de test)
{
  "id": 313,
  "businessId": 186,
  "name": "Amira Ben Salah",
  "email": "[email protected]",
  "phone": "+21698123456",
  "points": {
    "spendable": 0,
    "locked": 0,
    "lifetimeEarned": 0
  },
  "createdAt": "2026-10-13T21:36:40.0873817",
  "updatedAt": "2026-10-13T21:36:40.0873817"
}
200 OK RéponseRéponse réelle de l’API (données de test)
{
  "id": 313,
  "businessId": 186,
  "name": "Amira B.",
  "email": "[email protected]",
  "phone": "+21698123456",
  "points": {
    "spendable": 0,
    "locked": 0,
    "lifetimeEarned": 0
  },
  "createdAt": "2026-10-13T21:36:40.087382",
  "updatedAt": "2026-10-13T21:36:40.1219263"
}
400 Bad Request RéponseRéponse réelle de l’API (données de test)
{
  "error": {
    "code": "CUSTOMER_IDENTIFIER_REQUIRED",
    "message": "Provide a phone or an email"
  }
}

Solde de points

GET/api/ext/v1/customers/313/points

Permission: customers:read

Points disponibles, bloqués et cumulés, avec le détail par programme (jeux / fidélité) et la carte à tampons.

Paramètres
NomOùTypeDescription
id*pathintegerId du client Dawwerni (obligatoire)
curl "https://app.dawwerni.com/api/ext/v1/customers/313/points" \
  -H "Authorization: Bearer $DAWWERNI_API_KEY"
200 OK RéponseRéponse réelle de l’API (données de test)
{
  "customerId": 313,
  "businessId": 186,
  "total": 192,
  "spendable": 192,
  "locked": 0,
  "lifetimeEarned": 192,
  "byProgram": {
    "GAMES": 0,
    "LOYALTY": 192
  },
  "balanceMode": "COMBINED",
  "loyaltyMode": "POINTS",
  "stampCount": 0,
  "stampsRequired": 10,
  "stampRewardsAvailable": 0
}

Enregistrer une vente (points)

POST/api/ext/v1/purchases

Permission: points:write

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
NomOùTypeDescription
externalId*bodystring ≤ 120Id unique du ticket dans votre caisse (obligatoire)
amount*bodynumber ≥ 0Montant payé (obligatoire)
customer*body{ id | phone | email, name }Client : id Dawwerni, ou téléphone / e-mail (+ nom) (obligatoire)
currencybodyISO 4217 (TND, EGP, SAR…)Devise ISO (TND, EGP, SAR, USD…) (optionnel)
pointsbodyinteger 0–1 000 000Points imposés (sinon calculés par votre règle) (optionnel)
itemsbody[{ sku, name, quantity, unitPrice }]Lignes du ticket (seul leur nombre est enregistré) (optionnel)
businessIdbodyintegerObligatoire si la clé couvre plusieurs commerces (optionnel)
Idempotency-Keyheaderstring ≤ 255Rejeu sans risque pendant 24 h (optionnel)
curl -X POST "https://app.dawwerni.com/api/ext/v1/purchases" \
  -H "Authorization: Bearer $DAWWERNI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: f3b0c442-98fc-4c1c-9a3a-6b0e5a1d2c01" \
  -d '{ "customer": { "phone": "+21698123456", "name": "Amira Ben Salah" }, "amount": 42.5, "currency": "TND", "externalId": "R-2026-000123", "items": [ { "sku": "LATTE", "name": "Latte", "quantity": 2, "unitPrice": 7.5 } ] }'
201 Created RéponseRéponse réelle de l’API (données de test)
{
  "purchaseId": 9,
  "externalId": "R-2026-000123",
  "duplicate": false,
  "businessId": 186,
  "amount": 42.5,
  "currency": "TND",
  "itemsCount": 1,
  "pointsRequested": null,
  "pointsAwarded": 42,
  "customer": {
    "id": 313,
    "businessId": 186,
    "name": "Amira Ben Salah",
    "email": "[email protected]",
    "phone": "+21698123456",
    "points": {
      "spendable": 42,
      "locked": 0,
      "lifetimeEarned": 42
    },
    "createdAt": "2026-10-13T21:36:40.087382",
    "updatedAt": "2026-10-13T21:36:40.2972964"
  },
  "balance": {
    "customerId": 313,
    "businessId": 186,
    "total": 42,
    "spendable": 42,
    "locked": 0,
    "lifetimeEarned": 42,
    "byProgram": {
      "GAMES": 0,
      "LOYALTY": 42
    },
    "balanceMode": "COMBINED",
    "loyaltyMode": "POINTS",
    "stampCount": 0,
    "stampsRequired": 10,
    "stampRewardsAvailable": 0,
    "program": "LOYALTY",
    "outcome": "OK"
  },
  "source": "api"
}
200 OK RéponseRéponse réelle de l’API (données de test)
{
  "purchaseId": 9,
  "externalId": "R-2026-000123",
  "duplicate": true,
  "businessId": 186,
  "amount": 42.5,
  "currency": "TND",
  "itemsCount": 1,
  "pointsRequested": null,
  "pointsAwarded": 0,
  "customer": {
    "id": 313,
    "businessId": 186,
    "name": "Amira Ben Salah",
    "email": "[email protected]",
    "phone": "+21698123456",
    "points": {
      "spendable": 42,
      "locked": 0,
      "lifetimeEarned": 42
    },
    "createdAt": "2026-10-13T21:36:40.087382",
    "updatedAt": "2026-10-13T21:36:40.2972964"
  },
  "balance": {
    "customerId": 313,
    "businessId": 186,
    "total": 42,
    "spendable": 42,
    "locked": 0,
    "lifetimeEarned": 42,
    "byProgram": {
      "GAMES": 0,
      "LOYALTY": 42
    },
    "balanceMode": "COMBINED",
    "loyaltyMode": "POINTS",
    "stampCount": 0,
    "stampsRequired": 10,
    "stampRewardsAvailable": 0,
    "program": "LOYALTY",
    "outcome": "OK"
  },
  "source": "api"
}
400 Bad Request RéponseRéponse réelle de l’API (données de test)
{
  "error": {
    "code": "EXTERNAL_ID_REQUIRED",
    "message": "externalId (POS receipt id, max 120 chars) is required"
  }
}

Lister les récompenses

GET/api/ext/v1/rewards

Permission: rewards:read

Récompenses actives de vos commerces : coût en points, stock et programme utilisable.

Paramètres
NomOùTypeDescription
businessIdqueryintegerLimiter à un commerce (optionnel)
pagequeryinteger ≥ 0Page, à partir de 0 (optionnel)
sizequeryinteger 1–100 (20)Taille de page (optionnel)
curl "https://app.dawwerni.com/api/ext/v1/rewards" \
  -H "Authorization: Bearer $DAWWERNI_API_KEY"
200 OK RéponseRéponse réelle de l’API (données de test)
{
  "data": [
    {
      "id": 34,
      "businessId": 186,
      "name": "Free cappuccino",
      "description": "One cappuccino, any size",
      "pointsCost": 100,
      "stock": 50,
      "program": "BOTH"
    }
  ],
  "page": {
    "number": 0,
    "size": 20,
    "totalElements": 1,
    "totalPages": 1,
    "hasNext": false
  }
}

Échanger une récompense

POST/api/ext/v1/redemptions

Permission: redemptions:write

Déduit les points, décrémente le stock et crée un échange EN ATTENTE visible dans vos pages fidélité.

  • Le client doit avoir au moins une participation à un jeu (sinon 422 NO_PARTICIPATION_FOR_CUSTOMER).
  • Avec externalId, un même échange renvoyé une deuxième fois n’est pas dupliqué ("duplicate": true).
Paramètres
NomOùTypeDescription
customerId*bodyintegerId du client Dawwerni (obligatoire)
rewardId*bodyintegerId de la récompense (GET /rewards) (obligatoire)
externalIdbodystring ≤ 120Votre référence, anti-doublon (optionnel)
notesbodystring ≤ 250Note interne (optionnel)
Idempotency-Keyheaderstring ≤ 255Rejeu sans risque pendant 24 h (optionnel)
curl -X POST "https://app.dawwerni.com/api/ext/v1/redemptions" \
  -H "Authorization: Bearer $DAWWERNI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "customerId": 313, "rewardId": 33, "externalId": "RD-1001", "notes": "Counter 1" }'
201 Created RéponseExemple construit à partir du code de l’API (nécessite une vraie participation à un jeu)
{
  "redemptionId": 57,
  "businessId": 186,
  "rewardId": 33,
  "rewardName": "Free cappuccino",
  "pointsUsed": 100,
  "status": "PENDING",
  "requestedAt": "2026-10-13T21:40:12.418302",
  "program": "LOYALTY",
  "customerId": 313,
  "externalId": "RD-1001",
  "duplicate": false
}
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
NomOùTypeDescription
code*bodystringJeton QR, URL de vérification ou référence à 8 caractères (obligatoire)
curl -X POST "https://app.dawwerni.com/api/ext/v1/winnings/verify" \
  -H "Authorization: Bearer $DAWWERNI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "code": "a1b2c3d4" }'
200 OK RéponseExemple construit à partir du code de l’API (nécessite une vraie participation à un jeu)
{
  "verified": true,
  "alreadyClaimed": false,
  "message": "Winning verified successfully",
  "winning": {
    "id": 9120,
    "ref": "a1b2c3d4",
    "businessId": 186,
    "gameId": 412,
    "prize": "Free croissant",
    "fullName": "Amira Ben Salah",
    "email": "[email protected]",
    "phone": "+21698123456",
    "status": "taked",
    "createdAt": "2026-10-12T17:05:44.120931"
  }
}
404 Not Found RéponseRéponse réelle de l’API (données de test)
{
  "error": {
    "code": "WINNING_NOT_FOUND",
    "message": "No gift matches this code"
  }
}
400 Bad Request RéponseRéponse réelle de l’API (données de test)
{
  "error": {
    "code": "INVALID_CODE",
    "message": "The code is not a valid Dawwerni gift code"
  }
}

Lister les webhooks

GET/api/ext/v1/webhooks

Permission: webhooks:manage

Vos endpoints, leurs événements et leur état de livraison.

Paramètres
NomOùTypeDescription
pagequeryinteger ≥ 0Page, à partir de 0 (optionnel)
sizequeryinteger 1–100 (20)Taille de page (optionnel)
curl "https://app.dawwerni.com/api/ext/v1/webhooks" \
  -H "Authorization: Bearer $DAWWERNI_API_KEY"
200 OK RéponseRéponse réelle de l’API (données de test)
{
  "data": [
    {
      "id": 2,
      "url": "https://example.com/webhooks/dawwerni",
      "description": "CRM sync",
      "events": [
        "winning.created",
        "points.earned"
      ],
      "active": false,
      "failureCount": 0,
      "lastSuccessAt": null,
      "lastFailureAt": null,
      "disabledAt": null,
      "disabledReason": null,
      "secretHint": "whsec_...9fQe",
      "createdAt": "2026-10-13T20:36:40.854516Z"
    }
  ],
  "page": {
    "number": 0,
    "size": 20,
    "totalElements": 1,
    "totalPages": 1,
    "hasNext": false
  }
}

Créer un webhook

POST/api/ext/v1/webhooks

Permission: webhooks:manage

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
NomOùTypeDescription
url*bodyhttps URLURL HTTPS publique de votre serveur (obligatoire)
events*bodystring[]Événements à recevoir (voir Webhooks) (obligatoire)
descriptionbodystring ≤ 255Libellé libre (optionnel)
activebodyboolean (true)false pour créer en pause (optionnel)
curl -X POST "https://app.dawwerni.com/api/ext/v1/webhooks" \
  -H "Authorization: Bearer $DAWWERNI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://crm.example.com/webhooks/dawwerni", "events": [ "points.earned", "winning.created" ], "description": "CRM sync" }'
201 Created RéponseRéponse réelle de l’API (données de test)
{
  "id": 2,
  "url": "https://example.com/webhooks/dawwerni",
  "description": "CRM sync",
  "events": [
    "winning.created",
    "points.earned"
  ],
  "active": false,
  "failureCount": 0,
  "lastSuccessAt": null,
  "lastFailureAt": null,
  "disabledAt": null,
  "disabledReason": null,
  "secretHint": "whsec_...9fQe",
  "createdAt": "2026-10-13T20:36:40.854516100Z",
  "secret": "whsec_<shown once: store it in your server config>"
}
400 Bad Request RéponseRéponse réelle de l’API (données de test)
{
  "error": {
    "code": "INVALID_WEBHOOK_URL",
    "message": "Only https:// URLs are allowed"
  }
}
400 Bad Request RéponseRéponse réelle de l’API (données de test)
{
  "error": {
    "code": "UNKNOWN_EVENT",
    "message": "Unknown event: order.paid"
  }
}

Supprimer un webhook

DELETE/api/ext/v1/webhooks/2

Permission: webhooks:manage

Supprime l’endpoint et annule ses livraisons en attente.

Paramètres
NomOùTypeDescription
id*pathintegerId de l’endpoint (obligatoire)
curl -X DELETE "https://app.dawwerni.com/api/ext/v1/webhooks/2" \
  -H "Authorization: Bearer $DAWWERNI_API_KEY"
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"
  }
}
HTTPCodeSignification
400VALIDATION_FAILEDParamètre manquant ou mal formé
400INVALID_JSONLe corps n’est pas un JSON valide
400CUSTOMER_IDENTIFIER_REQUIREDTéléphone ou e-mail requis
400INVALID_PHONETéléphone invalide (format international conseillé)
400INVALID_EMAILE-mail invalide
400EXTERNAL_ID_REQUIREDexternalId manquant (120 caractères max.)
400INVALID_AMOUNTMontant manquant ou négatif
400INVALID_CURRENCYDevise hors format ISO 4217
400INVALID_POINTSpoints hors de 0 à 1 000 000
400BUSINESS_REQUIREDLa clé couvre plusieurs commerces : envoyez businessId
400INVALID_IDEMPOTENCY_KEYIdempotency-Key : 1 à 255 caractères ASCII
400INVALID_EXTERNAL_IDexternalId trop long
400CODE_REQUIREDcode manquant
400INVALID_CODECe n’est pas un code cadeau Dawwerni
400INVALID_WEBHOOK_URLURL refusée (HTTPS public uniquement)
400EVENTS_REQUIREDChoisissez au moins un événement
400UNKNOWN_EVENTÉvénement inconnu
401UNAUTHENTICATEDAucune clé API envoyée
401INVALID_API_KEYClé API invalide
401API_KEY_REVOKEDClé API révoquée
403ACCOUNT_INACTIVECompte Dawwerni inactif
403FEATURE_NOT_ENABLEDIntégrations non incluses dans l’offre
403INSUFFICIENT_SCOPEPermission manquante sur la clé
403BUSINESS_FORBIDDENCommerce hors de portée de la clé
403BUSINESS_UNAVAILABLELe commerce de la clé a été supprimé
404CUSTOMER_NOT_FOUNDClient introuvable
404REWARD_NOT_FOUNDRécompense introuvable
404WINNING_NOT_FOUNDAucun cadeau pour ce code
404WEBHOOK_NOT_FOUNDEndpoint webhook introuvable
409WINNING_NOT_CLAIMABLEParticipation sans cadeau (perdue ou annulée)
409IDEMPOTENCY_CONFLICTMême Idempotency-Key en cours de traitement : réessayez
409DUPLICATE_EXTERNAL_IDMême externalId en cours de traitement : réessayez
415UNSUPPORTED_MEDIA_TYPEEnvoyez Content-Type: application/json
422IDEMPOTENCY_KEY_REUSEDIdempotency-Key déjà utilisée pour une autre requête
422INSUFFICIENT_POINTSPoints insuffisants
422REWARD_INACTIVERécompense désactivée
422REWARD_OUT_OF_STOCKRécompense en rupture
422NO_PARTICIPATION_FOR_CUSTOMERLe client n’a encore joué à aucun jeu
422REDEMPTION_REJECTEDÉchange refusé par les règles de fidélité
422WEBHOOK_LIMIT_REACHED10 endpoints maximum
422NO_BUSINESSLe compte n’a aucun commerce actif
429RATE_LIMITEDTrop de requêtes : attendez Retry-After secondes
500INTERNAL_ERRORErreur 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’applicationMenuIntégrationsWebhooksAjouter un endpoint

Événements

ÉvénementEnvoyé quand
participant.createdUn joueur laisse ses coordonnées dans un jeu (gagnant ou non)
winning.createdUn joueur gagne un cadeau
winning.claimedUn cadeau est remis (scan du QR, changement de statut ou /winnings/verify)
points.earnedDes points sont crédités par une vente reçue de l’API ou d’une caisse connectée
reward.redeemedUne récompense est échangée via l’API

Exemple de contenu

{
  "id": "evt_8f2k4m9q1x7w3z5r0t6y2u4p",
  "type": "points.earned",
  "createdAt": "2026-10-13T20:36:40.512Z",
  "apiVersion": "v1",
  "data": {
    "customerId": 313,
    "businessId": 186,
    "points": 42,
    "reason": "purchase",
    "externalId": "R-2026-000123",
    "amount": 42.5,
    "currency": "TND",
    "source": "api",
    "balance": {
      "customerId": 313,
      "businessId": 186,
      "total": 42,
      "spendable": 42,
      "locked": 0,
      "lifetimeEarned": 42,
      "byProgram": {
        "GAMES": 0,
        "LOYALTY": 42
      },
      "program": "LOYALTY",
      "outcome": "OK"
    }
  }
}

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’applicationMenuIntégrationsConnexions caisseConnecter

  • 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 :

externalIdId unique du ticket (obligatoire)
amountTotal payé (obligatoire)
currencyDevise ISO, optionnelle
type"sale" (par défaut) ; "refund" est ignoré
customerphone, email, name : téléphone ou e-mail requis pour gagner des points
itemsLignes du ticket, optionnelles
cURL
curl -X POST "$DAWWERNI_POS_URL" \
  -H "Content-Type: application/json" \
  -d '{ "externalId": "R-2026-000200", "amount": 31.5, "currency": "TND", "type": "sale", "customer": { "phone": "+21698123456", "name": "Amira" }, "items": [ { "sku": "CROISSANT", "name": "Croissant", "quantity": 3, "unitPrice": 2.5 } ] }'
200 OK RéponseRéponse réelle de l’API (données de test)
{
  "received": 1,
  "processed": 1,
  "duplicates": 0,
  "ignored": 0,
  "failed": 0
}

Plusieurs tickets d’un coup

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 ».

Requête
{
  "purchases": [
    {
      "externalId": "R-2026-000200",
      "amount": 31.5,
      "currency": "TND",
      "type": "sale",
      "customer": {
        "phone": "+21698123456",
        "name": "Amira"
      },
      "items": [
        {
          "sku": "CROISSANT",
          "name": "Croissant",
          "quantity": 3,
          "unitPrice": 2.5
        }
      ]
    },
    {
      "externalId": "R-2026-000201",
      "amount": 12,
      "currency": "TND",
      "customer": {
        "email": "[email protected]"
      }
    },
    {
      "externalId": "R-2026-000202",
      "amount": 9,
      "type": "refund"
    }
  ]
}
200 OKRéponse réelle de l’API (données de test)
{
  "received": 3,
  "processed": 1,
  "duplicates": 1,
  "ignored": 1,
  "failed": 0
}

Signature optionnelle

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

400INVALID_JSONLe corps n’est pas un JSON valide
400PAYLOAD_TOO_LARGECorps limité à 512 Ko
401INVALID_CONNECTION_TOKENConnexion inconnue ou jeton invalide
401INVALID_SIGNATURESignature absente, périmée ou fausse
403CONNECTION_PAUSEDConnexion en pause dans Dawwerni
403FEATURE_NOT_ENABLEDIntégrations non incluses dans l’offre
404UNKNOWN_CONNECTORType de caisse inconnu dans l’URL
422UNSUPPORTED_PAYLOADContenu illisible
429RATE_LIMITED120 é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’applicationMenuCampagnesSite web et boutique en ligneNouveau 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’applicationMenuProspectsFormulairesPartagerInté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>

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.