Aller au contenu principal

Webhooks

DZBuild envoie une requête HTTPS POST à l'URL de webhook de votre application quand un événement se produit sur une boutique qui l'a installée. Chaque requête est signée avec le secret de signature de votre application, ce qui permet à votre serveur de vérifier qu'elle vient bien de DZBuild avant d'agir.

URL de webhook​

Vous définissez une URL de webhook et les événements voulus sur votre application dans la console développeur. La même URL reçoit les événements de toutes les boutiques qui installent l'application, et le champ store_id de chaque payload indique la boutique concernée.

L'URL doit respecter ces règles, sinon la console refuse de l'enregistrer :

  • https uniquement, sur le port 443.
  • Un nom d'hôte public avec un domaine de premier niveau. Les adresses IP, identifiants et mots de passe dans l'URL sont refusés.
  • 255 caractères au maximum.
  • Pas sur un domaine DZBuild (dzbuild.app, dzbuild.com, dzbuild.me, dzme.app, minacef.com, twarc.net) ni sur workers.dev.

La dernière règle signifie qu'un Worker sur une URL workers.dev gratuite ne peut pas recevoir de webhooks. Placez-le sur un domaine que vous possédez dans votre compte Cloudflare (les domaines personnalisés sont disponibles avec l'offre gratuite de Cloudflare), enregistrez cette URL, puis vérifiez-la.

S'il ne vous faut que les nouvelles commandes, interrogez l'API à la place : un GET /v1/orders?since=<last created_at>&limit=50 par boutique et par minute reste dans les 120 requêtes par minute d'un jeton d'installation, et c'est ce que fait le preset cloudflare-orders par défaut. Les changements de statut, eux, passent toujours par les webhooks.

Quand vous cliquez sur « Vérifier le webhook » dans la console, DZBuild envoie une requête webhook.verify à l'URL. Répondez avec n'importe quel statut 2xx en moins de 6 secondes. Votre URL ne reçoit de vrais événements qu'une fois vérifiée. Une vérification réussie réactive aussi la livraison pour chaque installation dont l'endpoint avait été désactivé après des échecs répétés.

Les événements de commande n'arrivent à une installation que si le marchand a accordé le scope orders:read, car le payload contient le nom, le téléphone et l'adresse de l'acheteur. Sans ce scope, l'installation ne reçoit aucun événement de commande, seulement app.uninstalled.

Événements​

ÉvénementEnvoyé quand
order.createdUne nouvelle commande est passée sur la boutique.
order.confirmedLe statut de la commande passe à confirmé.
order.processingLe statut de la commande passe à en préparation.
order.shippedLe statut de la commande passe à expédié.
order.deliveredLe statut de la commande passe à livré.
order.cancelledLe statut de la commande passe à annulé.
order.returnedLe statut de la commande passe à retourné.
app.uninstalledLe marchand désinstalle votre application de la boutique. Vous le recevez même sans l'avoir choisi.
webhook.verifyVous cliquez sur « Vérifier le webhook » dans la console développeur.

DZBuild vérifie les nouvelles commandes et les changements de statut une fois par minute. order.created part au premier passage qui voit la nouvelle commande : comptez une à deux minutes. Chaque événement de statut est envoyé au plus une fois par commande.

Payload​

Chaque corps de requête est une enveloppe JSON. Cet exemple de commande est abrégé :

{
"id": "evt_9b2f6c1d04e8a7b35c6d2e10",
"event": "order.confirmed",
"created_at": "2026-09-26T14:05:09+01:00",
"store_id": 1234,
"data": {
"order": { "id": 98765, "order_number": "ORD-1234-20260926-1a2b3c4d", "status": "confirmed" },
"items": [ { "product_id": 555, "product_name": "Sac en cuir", "quantity": 1 } ],
"previous_status": "pending"
}
}
ChampSignification
idIdentifiant de l'événement. Il reste le même à chaque nouvelle tentative du même événement : utilisez-le pour écarter les doublons.
eventLe nom de l'événement.
created_atDate de création de l'enveloppe, au format ISO 8601 avec décalage UTC.
store_idLa boutique concernée. Il vaut 0 sur webhook.verify.
dataLes données de l'événement.

Pour les événements de commande, data.order contient ces champs : id, store_id, order_number, store_seq, status, payment_status, payment_method, delivery_type, desk_name, customer_name, customer_phone, customer_email, wilaya_id, wilaya_name (nom en arabe), commune, address, subtotal, shipping_cost, discount, total, promo_code, customer_notes, tracking_number, delivery_company, created_at, updated_at. data.items est une liste avec product_id, variant_id, product_name, variant_name, sku, price, quantity, total et source par ligne. Les événements de statut portent aussi data.previous_status.

En-têtes de livraison​

En-têteValeur
Content-Typeapplication/json
X-DZ-TimestampHeure Unix en secondes au moment de la signature.
X-DZ-Signaturet=<timestamp>,v1=<signature>
X-DZ-EventLe nom de l'événement, identique à event dans le corps.
X-DZ-DeliveryL'identifiant de livraison. Il reste le même entre les tentatives d'une livraison. La requête webhook.verify de la console n'en a pas.
User-AgentDZBuild-Webhooks/1.0

Signature​

X-DZ-Signature contient deux parties séparées par une virgule :

  • t est l'horodatage Unix, la même valeur que X-DZ-Timestamp.
  • v1 est le HMAC-SHA256 en hexadécimal minuscule de la chaîne t + . + le corps brut de la requête, avec le secret de signature de votre application comme clé.

Pour vérifier une requête :

  1. Lisez les octets bruts du corps avant tout décodage JSON. Un corps décodé puis réencodé ne correspondra pas.
  2. Calculez le HMAC-SHA256 avec votre secret de signature sur l'horodatage, un point, puis le corps brut.
  3. Comparez le résultat à v1 en temps constant.
  4. Rejetez la requête si l'horodatage s'écarte de plus de 5 minutes de votre horloge. Cela empêche de rejouer une ancienne requête.

Votre secret de signature s'affiche dans la console développeur. Gardez-le uniquement sur votre serveur. Quand vous le régénérez, toutes les installations signent avec le nouveau secret dès la livraison suivante, sans période de chevauchement : déployez le nouveau secret sur votre serveur au même moment.

Node.js​

const crypto = require('crypto');
const express = require('express');

const app = express();
const SECRET = process.env.DZBUILD_SIGNING_SECRET;

function parseSignature(header) {
const parts = {};
for (const pair of (header || '').split(',')) {
const i = pair.indexOf('=');
if (i > 0) parts[pair.slice(0, i)] = pair.slice(i + 1);
}
return parts;
}

app.post('/dzbuild/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
const { t, v1 } = parseSignature(req.get('X-DZ-Signature'));
if (!t || !v1 || !/^\d+$/.test(t)) return res.status(401).end();

const expected = crypto.createHmac('sha256', SECRET).update(t + '.').update(req.body).digest('hex');
const valid = v1.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
if (!valid) return res.status(401).end();
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return res.status(401).end();

const event = JSON.parse(req.body.toString('utf8'));
// Queue event for processing, then answer fast.
res.status(200).end();
});

PHP​

<?php
$secret = getenv('DZBUILD_SIGNING_SECRET');
$raw = file_get_contents('php://input');

$parts = [];
foreach (explode(',', $_SERVER['HTTP_X_DZ_SIGNATURE'] ?? '') as $pair) {
[$k, $v] = array_pad(explode('=', $pair, 2), 2, '');
$parts[$k] = $v;
}
$t = $parts['t'] ?? '';
$v1 = $parts['v1'] ?? '';

$expected = hash_hmac('sha256', $t . '.' . $raw, $secret);
if (!ctype_digit($t) || !hash_equals($expected, $v1) || abs(time() - (int) $t) > 300) {
http_response_code(401);
exit;
}

$event = json_decode($raw, true);
// Queue $event for processing, then answer fast.
http_response_code(200);

Python​

import hashlib
import hmac
import json
import os
import time

from flask import Flask, abort, request

app = Flask(__name__)
SECRET = os.environ["DZBUILD_SIGNING_SECRET"].encode()


@app.post("/dzbuild/webhooks")
def dzbuild_webhook():
raw = request.get_data()
parts = dict(
pair.split("=", 1)
for pair in request.headers.get("X-DZ-Signature", "").split(",")
if "=" in pair
)
t = parts.get("t", "")
v1 = parts.get("v1", "")

expected = hmac.new(SECRET, t.encode() + b"." + raw, hashlib.sha256).hexdigest()
if not t.isdigit() or not hmac.compare_digest(expected, v1):
abort(401)
if abs(time.time() - int(t)) > 300:
abort(401)

event = json.loads(raw)
# Queue event for processing, then answer fast.
return "", 200

Nouvelles tentatives et livraisons abandonnées​

Une livraison réussit quand votre serveur répond avec un statut 2xx en moins de 10 secondes. Tout autre statut, une redirection (3xx), un délai dépassé ou une erreur de connexion compte comme une tentative échouée. Les redirections ne sont pas suivies.

Chaque livraison a droit à 5 tentatives au maximum. Après une tentative échouée, la suivante attend au moins :

Tentative échouéeAttente avant la tentative suivante
160 secondes
25 minutes
330 minutes
42 heures
5Aucune. La livraison est marquée comme abandonnée et n'est plus jamais envoyée.

Ces attentes sont des minimums, car l'envoi tourne une fois par minute. La livraison est « au moins une fois » : une requête peut arriver deux fois, par exemple après une panne réseau du côté de DZBuild. Écartez les doublons grâce à l'id de l'enveloppe.

Après 10 tentatives échouées d'affilée, toutes livraisons confondues, l'endpoint de cette installation est désactivé et ses livraisons en attente sont abandonnées. Corrigez votre serveur, puis cliquez sur « Vérifier le webhook » dans la console développeur pour réactiver la livraison. Les livraisons déjà abandonnées ne sont pas renvoyées.

Les livraisons en attente sont aussi abandonnées sans envoi quand DZBuild suspend votre application, quand le plan de la boutique passe sous le plan minimum de votre application, ou quand une application en mode test tourne sur une boutique qui n'appartient pas au développeur. app.uninstalled fait exception et part quand même.

app.uninstalled​

Quand un marchand désinstalle votre application, DZBuild révoque le jeton d'installation, abandonne toutes les livraisons en attente de cette installation et envoie un événement app.uninstalled si l'endpoint de l'installation est actif et vérifié :

{
"id": "evt_5a0c7e3b9d1f24681ace3579",
"event": "app.uninstalled",
"created_at": "2026-09-26T14:05:09+01:00",
"store_id": 1234,
"data": {
"install_id": 57,
"client_id": "dzapp_4e1b9c07d2a86f35e0b1",
"store_id": 1234,
"uninstalled_at": "2026-09-26T13:05:09+00:00"
}
}

uninstalled_at est en UTC. L'avis suit le même calendrier de tentatives, et l'endpoint reste actif un jour après la désinstallation pour que les tentatives aboutissent. Si le marchand réinstalle votre application sur cette boutique avant la livraison de l'avis, l'avis en attente est abandonné pour ne pas annuler la nouvelle installation.

Après la désinstallation, les appels à l'API avec l'ancien jeton d'installation répondent 401. Traitez les données de la boutique comme le décrivent les règles de sécurité.

Pas de X-DZ-Token pour les applications​

Les webhooks créés par un marchand dans le tableau de bord DZBuild portent aussi un en-tête X-DZ-Token, une copie en clair du secret destinée aux outils no-code. Les livraisons aux applications ne le portent jamais : un seul secret de signature couvre toutes les boutiques qui installent votre application, et une copie dans un journal de requêtes permettrait à n'importe qui de forger des événements pour toutes. Vérifiez toujours X-DZ-Signature.

Cette page pour les outils IAVoir en MarkdownOuvrir dans ChatGPTOuvrir dans Claude