Aller au contenu principal

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

{
"email": "[email protected]",
"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"
}
ChampTypeRequisNotes
emailstringun parmi email/phone/external_user_idUtilisé pour dédup. Stocké comme sha256(lowercase) uniquement.
phonestringStocké comme sha256(value) uniquement.
external_user_idstring ≤ 190Votre ID interne pour l'utilisateur. Utile si pas d'email/phone.
sourcestring ≤ 64Libellé libre (slug page, campagne…).
countrystring (2 chars)ISO 3166-1 alpha-2. On uppercase.
ipstringStocké comme sha256(value) pour détection de fraude. N'envoyez que si pertinent.
metaobjectTout le reste. Stocké en JSON.
nonce32-hex stringUsage 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 :

  1. (store_id, nonce) UNIQUE — protège des rejeux accidentels du même appel.
  2. (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 pasusage.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.

HTTPCode / MessageCause
400bad_request "Body must be valid JSON"Body non-JSON
401unauthorized "Missing signature headers"En-têtes X-DZ-* manquants
401unauthorized "Timestamp out of window"Dérive d'horloge > 5 min
401unauthorized "Nonce reused"Même nonce deux fois en 1 h
401unauthorized "Signature mismatch"Inputs HMAC erronés
401unauthorized "Invalid or revoked public key"Clé révoquée ou ID erroné
402quota_exceededQuota mensuel d'inscriptions épuisé
429rate_limitedBurst 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_bytes en PHP). Jamais de recyclage.
  • Envoyez external_user_id mê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.counted si besoin de confirmation que la ligne a atterri.