Passer au contenu principal

Enregistrer un webhook

Créer, lister, tester et supprimer des abonnements webhook pour votre boutique.

Écrit par Support

L'API v1 n'applique aucune limite de webhooks par boutique — enregistrez autant d'URLs que votre intégration en a réellement besoin, et supprimez celles que vous n'utilisez plus. (L'addon Webhooks marchand sans code, lui, plafonne bien les endpoints : 1 en Unlimited, 3 en Enterprise.)

Chaque webhook peut s'abonner à une liste différente d'événements ; choisissez un modèle qui vous convient :

  • Un seul endpoint, tous les events — le plus simple pour des petites apps. Branchez sur event dans le handler.

  • Plusieurs endpoints, un event chacun — plus propre en microservices, mais plus d'URLs à gérer.

⚠️ Attention — Accès pilote

Les webhooks API v1 demandent une clé API du marchand, d'une boutique avec un plan Enterprise actif ; tout autre plan, ou un abonnement expiré, reçoit 403 forbidden "API access requires an active Enterprise plan". Les clés générées sur /dashboard/api sont déjà inscrites au pilote ; une clé non inscrite reçoit 403 forbidden "API is in pilot mode; key not enrolled". Les jetons d'application installée reçoivent 403 forbidden "Apps cannot use this endpoint" sur chaque appel sous /v1/webhooks.

POST /v1/webhooks — enregistrer

Auth : clé plateforme avec webhooks:write. Nécessite Idempotency-Key. Contrairement à la plupart des écritures, cette réponse n'est jamais mise en cache pour le rejeu, car elle contient le secret : réessayer avec la même clé enregistre un second webhook avec un nouveau secret. Si un appel expire, vérifiez GET /v1/webhooks avant de réessayer et supprimez tout doublon.

Corps

Champ

Type

Requis

Notes

url

string (URL https)

✅

Doit être https:// ; une URL http:// est refusée. La longueur maximale est de 500 caractères ; une URL plus longue est rejetée en 500 server_error, pas en erreur de validation.

events

string[]

✅

Liste de noms d'events. Voir Catalogue d'events. Vide = erreur.

Requête

curl -X POST 'https://api.dzbuild.app/v1/webhooks' \
  -H "Authorization: Bearer $DZ_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "url":    "https://yourapp.example/webhooks/dzbuild",
    "events": ["order.created", "order.confirmed", "order.shipped",
               "order.cancelled", "signup.counted"]
  }'

Réponse 200

{
  "data": {
    "id":     17,
    "secret": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
    "note":   "Save the secret now — it is not retrievable after this response."
  },
  "meta": { "request_id": "...", "api_version": "v1" }
}

💡 Astuce — Ne branchez jamais sur == 201

Les endpoints de création /v1/webhooks, /v1/products, /v1/orders, /v1/landing-pages et /v1/keys répondent 200 en cas de succès, tandis que d'autres, comme /v1/categories, répondent 201. Branchez plutôt sur n'importe quel 2xx.

Le secret est affiché UNE FOIS. Stockez-le à côté de l'id du webhook dans votre coffre : chaque livraison vers ce webhook est signée avec lui (voir Signature). Si vous le perdez, supprimez le webhook et créez-en un nouveau.

Erreurs

HTTP

Code / Message

Cause

400

bad_request "url must be a valid http(s) URL"

URL mal formée

400

bad_request "url must be https"

Une URL en http://

400

bad_request "url host must be a hostname, not an IP literal"

Une adresse IP au lieu d'un nom d'hôte

400

bad_request "url host is missing or carries credentials"

Une partie user:password@ dans l'URL

400

bad_request "url host must be a public hostname"

Un hôte qui n'est pas un nom de domaine complet, comme localhost

400

bad_request "url host does not resolve"

Le nom d'hôte n'a aucun enregistrement A ou AAAA

400

bad_request "url must resolve to a public address"

L'hôte pointe vers au moins une adresse privée ou réservée

400

bad_request "url port must be 80 or 443"

Un port personnalisé comme :8443

400

bad_request "Body must be valid JSON"

Le body de la requête n'est pas du JSON valide

400

bad_request "events must be a non-empty list"

Tableau vide

400

bad_request "unknown event: foo. Allowed: …"

Nom d'event hors catalogue

400

bad_request "Idempotency-Key header is required for write requests"

Idempotency-Key manquant sur un POST/DELETE

400

bad_request "Idempotency-Key must be <=64 chars, [A-Za-z0-9_-:.]"

Clé trop longue, ou contenant des caractères hors de cet ensemble (les +, /, = du base64 sont tous rejetés)

403

forbidden "API is in pilot mode; key not enrolled"

La clé existe mais n'est pas inscrite au pilote

403

forbidden "API access requires an active Enterprise plan"

La boutique n'a pas de plan Enterprise actif

403

forbidden "Apps cannot use this endpoint"

Le jeton appartient à une application installée

403

forbidden "Missing scope: webhooks:write"

La clé n'a pas le scope webhooks:write

500

server_error "Could not register webhook"

Généralement une url de plus de 500 caractères

GET /v1/webhooks — liste

Auth : clé plateforme avec webhooks:read.

curl https://api.dzbuild.app/v1/webhooks \
  -H "Authorization: Bearer $DZ_KEY"

{
  "data": {
    "items": [
      {
        "id":              17,
        "url":             "https://yourapp.example/webhooks/dzbuild",
        "events":          ["order.created", "order.confirmed"],
        "status":          "active",
        "last_success_at": "2026-04-30 21:18:23",
        "last_failure_at": null,
        "failure_count":   0,
        "created_at":      "2026-04-30 19:00:00"
      }
    ],
    "allowed_events": [
      "order.created", "order.confirmed", "order.shipped", "order.delivered",
      "order.cancelled", "order.returned", "payment.received",
      "signup.counted", "event.recorded", "product.stock_low"
    ]
  }
}

allowed_events est la liste que l'API accepte à l'enregistrement — mais elle est plus large que ce qui se déclenche réellement. payment.received, event.recorded et product.stock_low sont acceptés puis jamais émis. Vérifiez le Catalogue d'événements avant de construire sur l'un d'eux.

Statut

Signification

active

Reçoit les livraisons. En pratique, c'est la seule valeur que vous verrez.

paused

Réservé pour un usage futur, non utilisé actuellement — et il n'existe pas de page dashboard pour les webhooks API v1.

dead

Réservé pour un usage futur, non utilisé actuellement. Il n'y a pas de désactivation automatique ; failure_count se contente de compter les tentatives échouées et retombe à 0 au succès suivant.

Pour arrêter les livraisons, supprimez le webhook.

POST /v1/webhooks/{id}/test

Déclenche une livraison webhook.test pour vérifier que votre endpoint est joignable et voir la forme de l'enveloppe.

webhook.test ne peut pas faire l'objet d'un abonnement : le mettre dans le tableau events à l'enregistrement renvoie 400 bad_request "unknown event: webhook.test. Allowed: …". Une livraison de test est envoyée au webhook ciblé quels que soient les événements auxquels il est abonné.

Auth : clé plateforme avec webhooks:write. Nécessite Idempotency-Key.

curl -X POST 'https://api.dzbuild.app/v1/webhooks/17/test' \
  -H "Authorization: Bearer $DZ_KEY" \
  -H "Idempotency-Key: test-17-$(date +%s)"

{ "data": { "tested": true, "note": "a webhook.test delivery was enqueued; check your endpoint" } }

La livraison de test ressemble à :

{
  "event":       "webhook.test",
  "store_id":    13,
  "occurred_at": "2026-04-30T21:24:17+00:00",
  "data":        { "ts": 1717112657 },
  "delivery_id": "9f2c41ab77e05d18"
}

Deux choses à savoir :

  • Elle est mise en file, pas synchrone. La réponse confirme seulement que la livraison a été enfilée ; le POST arrive peu après, généralement dans la minute.

  • Elle teste votre code de vérification. Elle est signée avec le secret de votre webhook comme toute autre livraison : si elle passe votre contrôle, elle prouve la joignabilité, la forme de la payload et la vérification de signature. Voir Signature.

DELETE /v1/webhooks/{id}

Auth : clé plateforme avec webhooks:write. Nécessite Idempotency-Key.

curl -X DELETE 'https://api.dzbuild.app/v1/webhooks/17' \
  -H "Authorization: Bearer $DZ_KEY" \
  -H "Idempotency-Key: del-17"

{ "data": { "deleted": true, "id": 17 } }

Après suppression : - Plus aucune livraison nouvelle n'est mise en file. - Toutes les livraisons déjà en file pour ce webhook sont abandonnées immédiatement. Rien n'est « tenté une dernière fois ». Si l'arriéré vous importe, suspendez vos écritures et laissez la file se vider (généralement moins d'une minute) avant de supprimer. - Le secret du webhook est désormais inutile.

Rejeu d'Idempotency-Key sur test et DELETE

L'API met en cache la réponse de chaque Idempotency-Key pendant 24 heures et la rejoue avec un en-tête Idempotency-Replay: 1. Deux conséquences :

  • Réutiliser une clé littérale comme del-17 dans les 24 heures rejoue la réponse en cache au lieu d'exécuter un nouvel appel. Utilisez une clé fraîche (ou une clé qui encode la tentative) dès que vous voulez vraiment que l'opération s'exécute.

  • Les réponses d'erreur sont mises en cache elles aussi. Une écriture ratée rejoue son propre 4xx pendant 24 heures sous la même clé — changez de clé après avoir corrigé la requête.

Le rejeu est garanti pendant 24 heures quel que soit l'hôte appelé, et répond toujours avec Idempotency-Replay: 1. Préférez tout de même https://api.dzbuild.app/v1 : le chemin dzbuild.com/api/v1 n'est qu'un alias, et certains chemins peuvent y être bloqués.

Exigences de l'endpoint

Votre URL webhook doit :

  • Répondre en 2xx au succès. Un 5xx, un 408 ou un 429 est re-tenté 1 min, 5 min, 30 min puis 2 h après l'échec, puis abandonné ; tout autre 4xx, et tout 3xx, envoie la livraison en file morte immédiatement (voir Comportement des retries).

  • Répondre dans les 10 secondes au total (5 secondes pour la connexion). Plus lent compte comme un timeout, re-tenté comme un 5xx.

  • Être en https:// et servir un TLS valide. Les certificats sont vérifiés strictement : un certificat auto-signé échoue à chaque tentative.

  • Ne pas rediriger. Location n'est pas suivi ; un 301/302 est un échec.

  • Accepter POST avec Content-Type: application/json.

  • Lire le body raw pour vérifier la signature (ne pas re-sérialiser).

  • Être idempotent — le même delivery_id PEUT arriver plusieurs fois.

Piège classique dans certains frameworks : un middleware re-encode le JSON avant que votre handler le voie, et le HMAC ne match plus. Solutions :

  • Express : express.raw({ type: 'application/json' }) pour la route webhook, puis JSON.parse(req.body) dans le handler.

  • Django : request.body est les bytes raw — c'est ce qu'on veut.

  • Laravel : $request->getContent() retourne le body raw.

  • PHP raw : file_get_contents('php://input').

Voir Vérifier les signatures pour le code en 4 langages.

Avez-vous trouvé la réponse à votre question ?