# Environnement & .env

Ce guide couvre comment porter vos credentials DZBuild API **en sécurité** dans votre application — dev local, staging, production. Que vous construisiez une vitrine Node.js, un back-office Laravel/Symfony, un pipeline Python, un microservice Go ou une fonction edge serverless, les règles sont les mêmes.

## Avant de commencer[​](#avant-de-commencer "Lien direct vers Avant de commencer")

* **Votre boutique doit être sur un plan Enterprise actif, et votre clé doit être inscrite au pilote.** L'API v1 est réservée au plan Enterprise et au pilote en production. Sur tout autre plan, ou dès que l'abonnement Enterprise expire, chaque appel renvoie `403 forbidden` `"API access requires an active Enterprise plan"`. Les clés générées dans **Paramètres → API** sont inscrites automatiquement ; une clé non inscrite renvoie `403 forbidden` `"API is in pilot mode; key not enrolled"` à chaque appel, aussi correcte que soit votre configuration.
* **Pointez toujours `DZBUILD_API_BASE` sur `https://api.dzbuild.app/v1`.** `dzbuild.com/api/v1/...` n'est qu'un alias de la même API : certains chemins peuvent y être bloqués. Le rate limiting par boutique (partagé par toutes les clés de la boutique) et le rejeu d'idempotence s'appliquent sur les deux hôtes.

## Ce que vous devez stocker[​](#ce-que-vous-devez-stocker "Lien direct vers Ce que vous devez stocker")

Pour une intégration vitrine / back-office typique :

| Variable                 | Exemple                             | Notes                                                                                                                                                                  |
| ------------------------ | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `DZBUILD_API_KEY`        | `dzpk_live_0123456789abcd.<48 hex>` | Le **bearer token complet**. **Traitez-le comme un mot de passe.**                                                                                                     |
| `DZBUILD_API_BASE`       | `https://api.dzbuild.app/v1`        | URL de base. En prod, utilisez cet hôte, jamais `dzbuild.com/api/v1` (juste un alias).                                                                                 |
| `DZBUILD_WEBHOOK_SECRET` | 64 caractères hex minuscules        | Secret par webhook retourné à l'enregistrement. Sans préfixe. Chaque livraison vers ce webhook est signée avec lui ; voir [Secrets de webhooks](#secrets-de-webhooks). |

### Anatomie du bearer token[​](#anatomie-du-bearer-token "Lien direct vers Anatomie du bearer token")

Les marchands le tronquent en permanence, autant l'expliciter. Le bearer token d'une clé plateforme est :

```
dzpk_live_<14 hex>.<48 hex>
```

73 caractères, tous en minuscules après le préfixe, avec **un seul point** au milieu. La partie `dzpk_live_…` avant le point est le **key id** — un identifiant, pas un credential. `Authorization: Bearer` a besoin de la chaîne entière de 73 caractères.

Se tromper là-dessus fait diverger les deux couches, ce qui est un diagnostic utile :

* Un token **sans aucun point** → `401 unauthorized "Invalid bearer format"`.
* Un token bien formé mais inconnu / révoqué → `401 unauthorized "Invalid or revoked API key"`.

Pour le **flow clé publique** (signups / events depuis un client public) :

| Variable                 | Exemple                                    | Notes                                                                   |
| ------------------------ | ------------------------------------------ | ----------------------------------------------------------------------- |
| `DZBUILD_PUBLIC_KEY_ID`  | `dzpub_live_...`                           | Safe à shipper dans le code client (l'**id** est public, le secret non) |
| `DZBUILD_SIGNING_SECRET` | 64 caractères hex minuscules, sans préfixe | **Server-side seulement** — sert à HMAC-signer chaque body de requête   |

## Fichiers .env[​](#fichiers-env "Lien direct vers Fichiers .env")

Le pattern le plus simple :

```
# .env (à la racine, gitignored)

DZBUILD_API_KEY=dzpk_live_0123456789abcd.0123456789abcdef0123456789abcdef0123456789abcdef

DZBUILD_API_BASE=https://api.dzbuild.app/v1

DZBUILD_WEBHOOK_SECRET=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
```

```
# .gitignore

.env

.env.local

.env.*.local
```

Committez un **`.env.example`** avec des placeholders pour que les autres devs sachent quelles vars définir :

```
# .env.example (committé)

DZBUILD_API_KEY=dzpk_live_REPLACE_ME

DZBUILD_API_BASE=https://api.dzbuild.app/v1

DZBUILD_WEBHOOK_SECRET=REPLACE_ME_64_HEX
```

### Charger .env par langage[​](#charger-env-par-langage "Lien direct vers Charger .env par langage")

#### Node.js / Next.js / Express[​](#nodejs--nextjs--express "Lien direct vers Node.js / Next.js / Express")

```
// next.config.js — Next.js charge .env, .env.local, .env.production automatiquement

// Pour Node simple :

import 'dotenv/config';

const key = process.env.DZBUILD_API_KEY;
```

#### Python[​](#python "Lien direct vers Python")

```
# pip install python-dotenv

from dotenv import load_dotenv

import os

load_dotenv()

key = os.environ['DZBUILD_API_KEY']
```

#### PHP / Laravel[​](#php--laravel "Lien direct vers PHP / Laravel")

```
// Laravel charge .env auto

$key = env('DZBUILD_API_KEY');



// PHP simple :

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);

$dotenv->load();

$key = $_ENV['DZBUILD_API_KEY'];
```

#### Go[​](#go "Lien direct vers Go")

```
// go get github.com/joho/godotenv

godotenv.Load()

key := os.Getenv("DZBUILD_API_KEY")
```

#### Flutter / mobile[​](#flutter--mobile "Lien direct vers Flutter / mobile")

**Ne shippez pas la clé plateforme dans votre app mobile.** À la place :

1. Votre app mobile appelle **votre** back-end.
2. Votre back-end détient la clé et proxie vers DZBuild.

Si votre app mobile doit enregistrer des `signups`, envoyez-les aussi à **votre** back-end. Chaque requête clé publique porte une signature HMAC calculée avec le secret de signature, et ce secret ne doit jamais être shippé dans une app : votre back-end signe et relaie l'appel, comme dans la section clé publique ci-dessous. Seul l'id de la clé publique peut être exposé sans risque. Voir [Endpoints clé publique](https://dzbuild.com/fr/fr/api-docs/public-key/signups.md).

## Environnements de production[​](#environnements-de-production "Lien direct vers Environnements de production")

En prod, **n'utilisez pas un fichier .env**. Utilisez le secret store natif de la plateforme :

### Vercel / Netlify / Cloudflare Pages[​](#vercel--netlify--cloudflare-pages "Lien direct vers Vercel / Netlify / Cloudflare Pages")

```
Projet → Settings → Environment Variables

   DZBUILD_API_KEY = dzpk_live_...

   DZBUILD_API_BASE = https://api.dzbuild.app/v1

   DZBUILD_WEBHOOK_SECRET = <64-char lowercase hex>
```

Marquez les vars prod-only en scope « Production ». Les vars staging en « Preview » ou env séparé.

### Cloudflare Workers[​](#cloudflare-workers "Lien direct vers Cloudflare Workers")

```
wrangler secret put DZBUILD_API_KEY

# (collez la valeur quand demandé)
```

Disponible dans le worker comme `env.DZBUILD_API_KEY` (avec `[vars]` déclaré dans `wrangler.toml`).

### AWS Lambda / API Gateway[​](#aws-lambda--api-gateway "Lien direct vers AWS Lambda / API Gateway")

Utilisez **AWS Secrets Manager** ou **Parameter Store** :

```
import boto3, json

secret = json.loads(

    boto3.client('secretsmanager').get_secret_value(SecretId='dzbuild/prod')['SecretString']

)

key = secret['DZBUILD_API_KEY']
```

Ne stockez pas la clé dans Lambda env vars en clair (ils apparaissent dans CloudTrail logs). Référencez le secret manager.

### Docker / Docker Compose[​](#docker--docker-compose "Lien direct vers Docker / Docker Compose")

```
# docker-compose.yml

services:

  app:

    image: yourapp:latest

    env_file:

      - .env.production    # NON committé

    environment:

      - NODE_ENV=production
```

Pour Kubernetes, utilisez un `Secret` :

```
apiVersion: v1

kind: Secret

metadata:

  name: dzbuild-creds

type: Opaque

stringData:

  DZBUILD_API_KEY: dzpk_live_...

  DZBUILD_WEBHOOK_SECRET: <64-char lowercase hex>
```

Référence dans le deployment :

```
envFrom:

  - secretRef:

      name: dzbuild-creds
```

### GitHub Actions[​](#github-actions "Lien direct vers GitHub Actions")

```
# .github/workflows/deploy.yml

env:

  DZBUILD_API_KEY: ${{ secrets.DZBUILD_API_KEY }}
```

Définissez le secret dans **Repo → Settings → Secrets and variables → Actions**.

## Clés dev vs production[​](#clés-dev-vs-production "Lien direct vers Clés dev vs production")

**Créez toujours deux clés séparées :**

* Une nommée `dev` ou `staging` — utilisée dans `.env.local` / dev
* Une nommée `production` — utilisée seulement en prod réelle

Si la clé dev fuit, vous ne brûlez que dev. Les données prod restent intactes.

### Comment obtenir une clé, concrètement[​](#comment-obtenir-une-clé-concrètement "Lien direct vers Comment obtenir une clé, concrètement")

Le propriétaire de la boutique génère les clés depuis le dashboard marchand, dans **Paramètres → API** (`/dashboard/api`). La boutique doit être sur un plan Enterprise actif, et elle détient au plus 3 clés actives.

1. **Votre première clé** vient de cette page : cliquez sur **Générer une clé API**, nommez-la d'après l'environnement où elle vivra (`myapp-dev`, `myapp-prod`) et copiez le bearer token, affiché une seule fois. Les clés créées là sont déjà inscrites au pilote.

2. **Les clés suivantes**, vous pouvez les créer vous-même à partir d'une clé existante :

   ```
   curl -X POST 'https://api.dzbuild.app/v1/keys' \

     -H "Authorization: Bearer $DZBUILD_API_KEY" \

     -H "Content-Type: application/json" \

     -H "Idempotency-Key: $(uuidgen)" \

     -d '{"type":"platform","name":"myapp-dev"}'

   # HTTP 200 → { "data": { "key_id", "bearer_token", "signing_secret", "note" } }
   ```

   La nouvelle clé hérite tel quel du tier de rate limit et du flag pilote de la clé appelante.

**Les scopes ne sont pas sélectionnables.** `POST /v1/keys` n'accepte que `type` et `name`. Une clé plateforme créée dans **Paramètres → API** reçoit l'ensemble par défaut complet : `store:read`, `store:write`, `products:read`, `products:write`, `orders:read`, `orders:write`, `customers:read`, `landing_pages:read`, `landing_pages:write`, `promos:read`, `promos:write`, `pixels:read`, `pixels:write`, `shipping:read`, `shipping:write`, `webhooks:read`, `webhooks:write`, `usage:read`, `analytics:read`, `whatsapp:read`, `whatsapp:send`. Une clé plateforme créée avec `POST /v1/keys` reçoit cet ensemble moins chaque scope que la clé appelante n'a pas, jamais davantage. Une clé publique reçoit toujours `signups:write` et `events:write`. La séparation dev / prod vient de l'usage de deux clés différentes, pas d'une restriction de permissions.

## Secrets de webhooks[​](#secrets-de-webhooks "Lien direct vers Secrets de webhooks")

Quand vous enregistrez un webhook, la réponse inclut un `secret` :

```
curl -X POST 'https://api.dzbuild.app/v1/webhooks' \

  -H "Authorization: Bearer $DZBUILD_API_KEY" \

  -H "Content-Type: application/json" \

  -H "Idempotency-Key: $(uuidgen)" \

  -d '{

    "url":    "https://yourapp.com/webhook",

    "events": ["order.created", "order.confirmed"]

  }'
```

Réponse — HTTP **200**, et seulement trois champs :

```
{

  "data": {

    "id":     42,

    "secret": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",

    "note":   "Save the secret now — it is not retrievable after this response."

  },

  "meta": { "request_id": "...", "api_version": "v1" }

}
```

Le `secret` est une chaîne nue de 64 caractères hex minuscules — il n'y a pas de préfixe `dzwh_sec_`. Il est affiché **une fois** ; sauvez-le immédiatement dans votre coffre. Si vous le perdez, supprimez le webhook et enregistrez-en un autre.

Vérifiez chaque livraison API v1 avec ce secret

Chaque livraison vers le webhook porte `X-DZ-Signature: t=<unix seconds>,v1=<hex>`, le HMAC-SHA256 de `t + "." + raw_body` calculé avec `DZBUILD_WEBHOOK_SECRET`. Recalculez-le, comparez à temps constant et rejetez un `t` à plus de 300 secondes de votre horloge. Un webhook enregistré avant l'arrivée de la signature par webhook garde une ancienne signature que ce secret ne peut pas vérifier : enregistrez ce webhook de nouveau.

La recette et le code en 4 langages se trouvent dans [Vérifier les signatures](https://dzbuild.com/fr/fr/api-docs/webhooks/verifying-signatures.md).

## Flow clé publique (signups / events)[​](#flow-clé-publique-signups--events "Lien direct vers Flow clé publique (signups / events)")

Le flow clé publique existe pour des endpoints à scope étroit (signup tracking, event tracking) où vous ne voulez pas de clé plateforme dans la boucle. Seul l'**id de la clé publique** n'est pas secret — le **secret de signature** reste sur votre back-end.

N'appelez pas `/v1/signups` directement depuis un navigateur

Trois choses cassent un appel navigateur direct aujourd'hui :

* **Pas de CORS.** L'API ne répond à aucun preflight : une requête `OPTIONS` sans clé reçoit le `401` habituel, sans en-tête `Access-Control-*`, et le navigateur bloque l'appel. L'API sert uniquement aux appels de serveur à serveur.
* **`Idempotency-Key` est obligatoire** sur chaque POST, et c'est facile à oublier côté client.
* **`crypto.randomUUID()` n'est pas un nonce valide.** Le nonce doit faire exactement 32 caractères hex minuscules ; un UUID de 36 caractères avec tirets renvoie `401 unauthorized "Invalid nonce format"`.

Passez par votre propre back-end à la place — le pattern ci-dessous.

```
// Navigateur : parlez à VOTRE endpoint, jamais à api.dzbuild.app

await fetch('/api/track-signup', {

  method: 'POST',

  headers: { 'Content-Type': 'application/json' },

  body: JSON.stringify({ email, external_user_id: externalId })

});
```

```
// Votre back-end (Node) : détient le secret de signature, signe et relaie.

import crypto from 'node:crypto';



const KEY_ID = process.env.DZBUILD_PUBLIC_KEY_ID;

const SECRET = process.env.DZBUILD_SIGNING_SECRET;



export async function trackSignup({ email, external_user_id }) {

  const nonce = crypto.randomBytes(16).toString('hex');   // 32 hex minuscules

  const ts    = Math.floor(Date.now() / 1000).toString();

  const body  = JSON.stringify({ email, external_user_id, source: 'web', nonce });

  const hash  = crypto.createHash('sha256').update(body).digest('hex');

  const sig   = crypto.createHmac('sha256', SECRET)

    .update(`${KEY_ID}\n${nonce}\n${ts}\n${hash}`).digest('hex');



  await fetch('https://api.dzbuild.app/v1/signups', {

    method: 'POST',

    headers: {

      'Authorization':   `DZ-Public ${KEY_ID}`,

      'X-DZ-Timestamp':  ts,

      'X-DZ-Nonce':      nonce,

      'X-DZ-Signature':  sig,

      'Idempotency-Key': nonce,

      'Content-Type':    'application/json'

    },

    body

  });

}
```

Le secret de signature ne quitte jamais votre back-end. L'id de clé publique peut être inspecté par n'importe qui — c'est voulu. Voir [Inscriptions](https://dzbuild.com/fr/fr/api-docs/public-key/signups.md) pour le contrat complet.

## Tunneling local pour les webhooks[​](#tunneling-local-pour-les-webhooks "Lien direct vers Tunneling local pour les webhooks")

Les webhooks DZBuild ont besoin d'une URL HTTPS publique. Pour tester localement :

```
# ngrok

ngrok http 3000



# Cloudflare Tunnel

cloudflared tunnel --url http://localhost:3000
```

Enregistrez l'URL du tunnel comme cible webhook. L'URL d'un webhook enregistré ne peut pas être modifiée : quand l'URL du tunnel change, supprimez le webhook, enregistrez la nouvelle URL et conservez le nouveau secret renvoyé par l'enregistrement (ngrok free change d'URL ; payez 8$/mois pour un sous-domaine stable).

## Politique de rotation[​](#politique-de-rotation "Lien direct vers Politique de rotation")

Faites tourner les clés :

* **Trimestriellement** pour les clés prod (mettez un rappel calendrier)
* **Immédiatement** si une clé peut avoir fuité (committée à git, screenshot, envoyée en chat)
* **Au départ d'un membre d'équipe** s'il avait accès au coffre

Procédure :

1. Créez une nouvelle clé avec `POST /v1/keys` (voir [Comment obtenir une clé, concrètement](#comment-obtenir-une-cl%C3%A9-concr%C3%A8tement)). Elle hérite du tier et du flag pilote de l'ancienne. Une boutique détient au plus 3 clés actives : à la limite, l'appel répond `400 bad_request` `"Key limit reached for this store (3 active keys). Revoke unused keys first."`, révoquez donc une clé inutilisée avant la rotation.
2. Mettez à jour votre coffre / env vars vers la nouvelle clé.
3. Déployez. Vérifiez que la nouvelle clé est utilisée : `GET /v1/keys` liste chaque clé avec son `last_used_at` (`GET /v1/usage` compte toute la boutique, pas une seule clé).
4. Après 24 h de trafic propre sur la nouvelle, **révoquez l'ancienne** : `DELETE /v1/keys/{old_key_id}`.

## Erreurs courantes[​](#erreurs-courantes "Lien direct vers Erreurs courantes")

| Erreur                                 | Conséquence                                                         | Fix                                                                      |
| -------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `.env` committé à git                  | La clé est dans l'historique git pour toujours ; rotation immédiate | `git filter-repo` ne corrige pas les forks/clones ; considérez compromis |
| Clé plateforme dans le code navigateur | Les clients voient et volent la clé dans network tab                | Move au back-end / serverless ; rotation                                 |
| `dzpk_live_...` hardcodé en source     | Pareil                                                              | env vars ; rotation                                                      |
| Même clé pour dev et prod              | Une boulette dev touche la prod                                     | Deux clés ; ne jamais partager                                           |
| Secret webhook perdu                   | Plus moyen de vérifier les livraisons                               | Supprimez le webhook, enregistrez-en un autre                            |
| Logguer la clé API dans les logs app   | Auditeurs / log shippers / quiconque a accès aux logs voit          | Redactez les secrets dans le logger ; rotation                           |

## Checklist avant prod[​](#checklist-avant-prod "Lien direct vers Checklist avant prod")

* [ ] `.env` est dans `.gitignore` (et n'a jamais été committé par accident)
* <!-- -->
  Clé prod nommée `prod` et utilisée seulement en prod
* <!-- -->
  Clé dev nommée `dev` et seulement en dev/staging
* <!-- -->
  Secrets webhooks dans secret manager, pas dans le code
* <!-- -->
  En-tête `Authorization` sur **chaque** appel API depuis le back-end
* <!-- -->
  Le code navigateur ne voit jamais `dzpk_live_*` (seulement `dzpub_live_*` si flow clé publique)
* <!-- -->
  Votre endpoint webhook vérifie `X-DZ-Signature` avec le secret du webhook et déduplique sur le `delivery_id` du body
* <!-- -->
  Rappel rotation de clés sur votre calendrier
* <!-- -->
  Logging qui ne capture PAS les bodies complets (peuvent contenir des clés API en client headers)
