POST /v1/signups
C'est l'unité métrée principale de l'API DZBuild. Chaque inscription qui passe par ici compte dans le quota mensuel de votre tier et contribue au pricing plateforme pour les intégrations partenaires.
Conçu pour un cas précis : vous avez un site/app externe qui accepte des inscriptions, et vous voulez qu'elles comptent pour votre compte marchand DZBuild. Exemples :
- Un site WordPress que vous gérez pour le marketing → l'utilisateur remplit le formulaire → vous appelez
/v1/signups. - Une app mobile où les utilisateurs s'inscrivent → backend appelle
/v1/signups. - Une landing page sur un autre domaine → backend appelle
/v1/signups.
Pas conçu pour les commandes vitrine (qui créent des clients via le flow vitrine) ni pour des inscriptions ponctuelles type lead-magnet (utilisez /v1/events).
Auth
Clé publique + HMAC. Votre backend signe chaque appel. Voir Authentification pour le schéma HMAC complet.
Corps
{
"phone": "+213555000000",
"external_user_id": "u_42",
"source": "landing-page-1",
"country": "DZ",
"ip": "203.0.113.42",
"meta": { "campaign": "spring-2026" },
"nonce": "32-hex-single-use"
}
| Champ | Type | Requis | Notes |
|---|---|---|---|
email | string | un parmi email/phone/external_user_id | Utilisé pour dédup. Stocké comme sha256(lowercase) uniquement. |
phone | string | Stocké comme sha256(value) uniquement. | |
external_user_id | string ≤ 190 | Votre ID interne pour l'utilisateur. Utile si pas d'email/phone. | |
source | string ≤ 64 | Libellé libre (slug page, campagne…). | |
country | string (2 chars) | ISO 3166-1 alpha-2. On uppercase. | |
ip | string | Stocké comme sha256(value) pour détection de fraude. N'envoyez que si pertinent. | |
meta | object | Tout le reste. Stocké en JSON. | |
nonce | 32-hex string | ✅ | Usage unique par clé, valable 1 h |
Vous devez inclure au moins un parmi email, phone ou external_user_id pour qu'on ait un identifiant stable pour la dédup.
Réponse 202 Accepted
{
"data": { "status": "queued", "kind": "signup", "store_id": 13 },
"meta": { "request_id": "...", "api_version": "v1", "edge": true }
}
Le 202 signifie « accepté en périphérie et mis en file pour persistance ». L'écriture DB réelle arrive sous 5 secondes via notre queue async. Vous n'attendez pas. Si vous avez besoin de confirmation immédiate, enregistrez un webhook signup.counted.
Règles de déduplication
Deux niveaux, tous deux au niveau base de données :
(store_id, nonce)UNIQUE — protège des rejeux accidentels du même appel.(store_id, email_hash)UNIQUE — un comptage par marchand par email, à vie.
Si un doublon touche l'une des contraintes, la ligne est insérée avec status = 'duplicate' et ne facture pas — usage.signup.billable reste plat. Vous pouvez auditer les doublons via GET /v1/usage/history.
Conséquence : tenter de gonfler vos chiffres en re-soumettant le même email ne marche pas. Et c'est aussi une feature — votre histoire d'idempotence est automatique.
Exemple : Node.js
import crypto from 'node:crypto';
const KEY_ID = process.env.DZ_PUBLIC_KEY;
const SECRET = process.env.DZ_SIGNING_SECRET;
export async function trackSignup({ email, phone, external_user_id, source, country, meta }) {
const nonce = crypto.randomBytes(16).toString('hex');
const ts = Math.floor(Date.now() / 1000).toString();
const body = JSON.stringify({ email, phone, external_user_id, source, country, meta, nonce });
const bodyHash = crypto.createHash('sha256').update(body).digest('hex');
const payload = `${KEY_ID}\n${nonce}\n${ts}\n${bodyHash}`;
const sig = crypto.createHmac('sha256', SECRET).update(payload).digest('hex');
const r = 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,
'Content-Type': 'application/json',
},
body,
});
if (!r.ok) {
const err = await r.json();
throw new Error(`signup failed: ${err.error?.code} ${err.error?.message}`);
}
return r.json();
}
Exemple : PHP (hook WordPress)
<?php
add_action('user_register', function($user_id) {
$user = get_userdata($user_id);
dz_track_signup([
'email' => $user->user_email,
'external_user_id' => "wp_{$user_id}",
'source' => 'wordpress-' . get_bloginfo('name'),
]);
});
function dz_track_signup(array $payload): void {
$keyId = getenv('DZ_PUBLIC_KEY');
$secret = getenv('DZ_SIGNING_SECRET');
$nonce = bin2hex(random_bytes(16));
$ts = (string) time();
$payload['nonce'] = $nonce;
$body = json_encode($payload, JSON_UNESCAPED_UNICODE);
$hash = hash('sha256', $body);
$sig = hash_hmac('sha256', "$keyId\n$nonce\n$ts\n$hash", $secret);
$ch = curl_init('https://api.dzbuild.app/v1/signups');
curl_setopt_array($ch, [
CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body,
CURLOPT_TIMEOUT => 5,
CURLOPT_HTTPHEADER => [
"Authorization: DZ-Public $keyId",
"X-DZ-Timestamp: $ts",
"X-DZ-Nonce: $nonce",
"X-DZ-Signature: $sig",
'Content-Type: application/json',
],
CURLOPT_RETURNTRANSFER => true,
]);
curl_exec($ch);
curl_close($ch);
}
Exemple : Python (signal Django)
import hashlib, hmac, json, os, secrets, time, requests
from django.dispatch import receiver
from django.contrib.auth.models import User
from django.db.models.signals import post_save
KEY_ID = os.environ['DZ_PUBLIC_KEY']
SECRET = os.environ['DZ_SIGNING_SECRET']
@receiver(post_save, sender=User)
def track_dz_signup(sender, instance, created, **kw):
if not created: return
payload = {
'email': instance.email, 'external_user_id': str(instance.pk),
'source': 'django-app', 'nonce': secrets.token_hex(16),
}
body = json.dumps(payload)
ts = str(int(time.time()))
h = hashlib.sha256(body.encode()).hexdigest()
sig = hmac.new(SECRET.encode(),
f"{KEY_ID}\n{payload['nonce']}\n{ts}\n{h}".encode(),
hashlib.sha256).hexdigest()
requests.post('https://api.dzbuild.app/v1/signups', data=body, timeout=5,
headers={
'Authorization': f'DZ-Public {KEY_ID}',
'X-DZ-Timestamp': ts, 'X-DZ-Nonce': payload['nonce'],
'X-DZ-Signature': sig, 'Content-Type': 'application/json',
})
Erreurs
Retournées en périphérie en ~10 ms — feedback rapide pour les requêtes mal formées.
| HTTP | Code / Message | Cause |
|---|---|---|
| 400 | bad_request "Body must be valid JSON" | Body non-JSON |
| 401 | unauthorized "Missing signature headers" | En-têtes X-DZ-* manquants |
| 401 | unauthorized "Timestamp out of window" | Dérive d'horloge > 5 min |
| 401 | unauthorized "Nonce reused" | Même nonce deux fois en 1 h |
| 401 | unauthorized "Signature mismatch" | Inputs HMAC erronés |
| 401 | unauthorized "Invalid or revoked public key" | Clé révoquée ou ID erroné |
| 402 | quota_exceeded | Quota mensuel d'inscriptions épuisé |
| 429 | rate_limited | Burst par minute dépassé |
Bonnes pratiques
- Signez côté backend, pas dans le navigateur. N'envoyez pas le secret aux utilisateurs.
- Générez des nonces frais avec un CSPRNG (
crypto.randomBytes,random.SystemRandom,random_bytesen PHP). Jamais de recyclage. - Envoyez
external_user_idmême si vous avez l'email — il survit aux changements d'email. - Ne retry pas automatiquement sur 401 — c'est permanent. Inspectez une fois, fixez le bug.
- Retry sur 5xx avec exponential backoff et un nonce frais à chaque fois.
- Abonnez-vous au webhook
signup.countedsi besoin de confirmation que la ligne a atterri.