Skip to main content

Environment & .env setup

This guide covers how to safely carry your DZBuild API credentials into your application — local dev, staging, production. Whether you're building a Node.js storefront, a Laravel/Symfony back-office, a Python data pipeline, a Go microservice, or a serverless edge function, the rules are the same.

What you need to store

For a typical storefront / back-office integration:

VariableExampleNotes
DZBUILD_API_KEYdzpk_live_aB3xT9...Platform key secret. Treat as a password.
DZBUILD_API_BASEhttps://api.dzbuild.app/v1Base URL. Use the edge host in prod, never dzbuild.com/api/v1 (it's only an alias).
DZBUILD_WEBHOOK_SECRETdzwh_sec_...Per-webhook secret returned at registration. Used to verify X-DZ-Signature.

For public-key flow (signups / events from a public client):

VariableExampleNotes
DZBUILD_PUBLIC_KEY_IDdzpub_live_...Safe to ship in client code (the id is public, the secret is not)
DZBUILD_SIGNING_SECRETsig_...Server-side only — used to HMAC-sign each request body

.env files

The simplest pattern:

# .env (in your project root, gitignored)
DZBUILD_API_KEY=dzpk_live_xxxxxxxxxxxxxxxx
DZBUILD_API_BASE=https://api.dzbuild.app/v1
DZBUILD_WEBHOOK_SECRET=dzwh_sec_xxxxxxxxxxxxxxxx
# .gitignore
.env
.env.local
.env.*.local

Commit a .env.example file with placeholder values so other developers know which vars to set:

# .env.example (committed)
DZBUILD_API_KEY=dzpk_live_REPLACE_ME
DZBUILD_API_BASE=https://api.dzbuild.app/v1
DZBUILD_WEBHOOK_SECRET=dzwh_sec_REPLACE_ME

Loading .env per language

Node.js / Next.js / Express

// next.config.js — Next.js auto-loads .env, .env.local, .env.production
// For plain Node:
import 'dotenv/config';
const key = process.env.DZBUILD_API_KEY;

Python

# pip install python-dotenv
from dotenv import load_dotenv
import os
load_dotenv()
key = os.environ['DZBUILD_API_KEY']

PHP / Laravel

// Laravel auto-loads .env
$key = env('DZBUILD_API_KEY');

// Plain PHP:
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
$key = $_ENV['DZBUILD_API_KEY'];

Go

// go get github.com/joho/godotenv
godotenv.Load()
key := os.Getenv("DZBUILD_API_KEY")

Flutter / mobile

Don't ship the platform key in your mobile app. Instead:

  1. Your mobile app calls your back-end.
  2. Your back-end holds the API key and proxies requests to DZBuild.

If you need the mobile client to talk directly to DZBuild (e.g. for signups), use the public-key flow — only the public key id ships, never the platform secret. See Public-key endpoints.

Production environments

In production, don't use a .env file. Use the platform's native secret store:

Vercel / Netlify / Cloudflare Pages

Project → Settings → Environment Variables
DZBUILD_API_KEY = dzpk_live_...
DZBUILD_API_BASE = https://api.dzbuild.app/v1
DZBUILD_WEBHOOK_SECRET = dzwh_sec_...

Mark prod-only vars as "Production" scope. Mark staging vars as "Preview" or a separate environment.

Cloudflare Workers

wrangler secret put DZBUILD_API_KEY
# (paste the value when prompted)

Available in the worker as env.DZBUILD_API_KEY (with [vars] declared in wrangler.toml).

AWS Lambda / API Gateway

Use AWS Secrets Manager or Parameter Store:

import boto3, json
secret = json.loads(
boto3.client('secretsmanager').get_secret_value(SecretId='dzbuild/prod')['SecretString']
)
key = secret['DZBUILD_API_KEY']

Don't store the key in Lambda env vars in plaintext (they appear in CloudTrail logs). Reference the secret manager.

Docker / Docker Compose

# docker-compose.yml
services:
app:
image: yourapp:latest
env_file:
- .env.production # NOT committed
environment:
- NODE_ENV=production

For Kubernetes, use a Secret:

apiVersion: v1
kind: Secret
metadata:
name: dzbuild-creds
type: Opaque
stringData:
DZBUILD_API_KEY: dzpk_live_...
DZBUILD_WEBHOOK_SECRET: dzwh_sec_...

Then reference in the deployment:

envFrom:
- secretRef:
name: dzbuild-creds

GitHub Actions

# .github/workflows/deploy.yml
env:
DZBUILD_API_KEY: ${{ secrets.DZBUILD_API_KEY }}

Set the secret in Repo → Settings → Secrets and variables → Actions.

Development vs production keys

Always mint two separate keys:

  • One named dev or staging — used in .env.local / dev environments
  • One named production — used only in your real prod deployment

If your dev key leaks, you only burn the dev key. Production data stays intact.

# Dashboard → Developer → API Keys
+ Create platform key → name "myapp-dev", scopes: products:read, orders:read, orders:write
+ Create platform key → name "myapp-prod", scopes: products:read, orders:read, orders:write

Use the same scopes on both, just different names.

Webhook secrets

When you register a webhook, the response includes a 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"]
}'

Response:

{
"data": {
"id": 42,
"url": "https://yourapp.com/webhook",
"secret": "dzwh_sec_aB3xT9...", ← store this in DZBUILD_WEBHOOK_SECRET
"events": ["order.created", "order.confirmed"]
}
}

This secret is shown once — save it immediately to your secret store. If you lose it, delete the webhook and register a new one.

Use the secret to verify each webhook delivery:

// Express webhook handler
app.post('/webhook', (req, res) => {
const sig = req.headers['x-dz-signature'];
const ts = req.headers['x-dz-timestamp'];
const body = req.rawBody; // ⚠️ raw, not parsed
const expected = crypto
.createHmac('sha256', process.env.DZBUILD_WEBHOOK_SECRET)
.update(`${ts}.${body}`)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return res.status(401).send('bad signature');
}
// ... process the event
});

See Verifying signatures for full code in 5 languages.

Public-key flow (browser / mobile)

If you need to call DZBuild directly from the browser or mobile app for narrow-scope endpoints (signup tracking, event tracking), use the public-key flow. Only the public key id ships in client code — the signing secret stays on your back-end and signs each request body.

// Browser code — safe
const PUBLIC_KEY_ID = "dzpub_live_xxxx"; // committed in client code is fine

// Get a signature from your server (which holds DZBUILD_SIGNING_SECRET)
async function trackSignup(email, externalId) {
const nonce = crypto.randomUUID();
const body = JSON.stringify({ email, external_user_id: externalId, source: 'web', nonce });
const ts = Math.floor(Date.now() / 1000);

// Your back-end signs it (NEVER do this in the browser)
const { signature } = await fetch('/sign-dz-event', {
method: 'POST',
body: JSON.stringify({ body_hash_input: body, ts, nonce })
}).then(r => r.json());

await fetch('https://api.dzbuild.app/v1/signups', {
method: 'POST',
headers: {
'Authorization': `DZ-Public ${PUBLIC_KEY_ID}`,
'X-DZ-Timestamp': ts.toString(),
'X-DZ-Nonce': nonce,
'X-DZ-Signature': signature,
'Content-Type': 'application/json'
},
body
});
}

The signing secret never leaves your back-end. The public key id can be inspected by anyone — that's by design.

Local-dev tunneling for webhooks

DZBuild webhooks need a public HTTPS URL. To test locally, use a tunnel:

# ngrok
ngrok http 3000

# Cloudflare Tunnel
cloudflared tunnel --url http://localhost:3000

# tailscale serve
tailscale serve https / http://localhost:3000

Register the tunnel URL as your webhook target. Update it whenever the tunnel restarts (ngrok free changes URL each time; pay $8/mo for a stable subdomain).

Rotation policy

Rotate keys:

  • Quarterly for production keys (set a calendar reminder)
  • Immediately if a key may have leaked (committed to git, screenshotted, sent in chat)
  • When a team member leaves if they had access to the secret store

Rotation procedure:

  1. Mint a new key in the dashboard with the same scopes.
  2. Update your secret store / env vars to the new key.
  3. Deploy. Verify traffic on the new key via GET /v1/usage.
  4. Once you confirm 24h of clean traffic on the new key, revoke the old one: DELETE /v1/keys/{old_key_id} or via dashboard.

Common mistakes

MistakeWhat happensFix
Committed .env to gitKey is now in git history forever; rotate immediatelygit filter-repo won't fix forks/clones; assume compromised
Used the platform key in browser codeCustomers can read your network tab → see and steal the keyMove to back-end / serverless function; rotate the leaked key
Hardcoded dzpk_live_... in sourceSame as aboveUse env vars; rotate
Same key for dev + prodA dev mistake hits prod dataMint two keys; never share
Lost webhook secretCan no longer verify deliveriesDelete the webhook, register a new one
Logged the API key in app logsAuditors / log shippers / anyone with log access sees itRedact secrets in your logger config; rotate

Quick checklist before going live

  • .env is in .gitignore (and was never accidentally committed)
  • Production key is named prod and used only in prod
  • Dev key is named dev and only in dev/staging
  • Webhook secrets stored in secret manager, not in code
  • Authorization header is on every API call from your back-end
  • Browser code never sees dzpk_live_* (only dzpub_live_* if you use public-key flow)
  • Signature verification is enabled on your webhook endpoint
  • You have a key rotation reminder on your calendar
  • You have logging that does NOT capture full request bodies (could include API keys in client headers)