POST /v1/signups
هذه هي الوحدة الرئيسية القابلة للقياس في DZBuild API. كل اشتراك يصل عبرها يُحسب ضمن الحصة الشهرية لخطتك ويسهم في تسعير المنصة لتكاملات الشركاء.
مصممة لحالة استخدام محددة: لديك موقع/تطبيق خارجي يقبل اشتراكات المستخدمين، وتريد أن يُحتسب كل واحد منها لحساب التاجر في DZBuild. أمثلة:
- موقع WordPress للتسويق → يملأ المستخدم نموذج التسجيل → تنادي
/v1/signups. - تطبيق جوال يسجّل المستخدمون فيه → الباك إند يُنادي
/v1/signups. - صفحة هبوط على نطاق آخر → الباك إند يُنادي
/v1/signups.
ليست مصممة لطلبات الواجهة (تلك تُنشئ عملاء عبر تدفق الواجهة نفسه) ولا لاشتراكات lead-magnet المعزولة (استخدم /v1/events لها).
المصادقة
مفتاح عام + HMAC. خادمك الخلفي يوقّع كل نداء. انظر المصادقة لمخطط HMAC الكامل.
الجسم
{
"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"
}
| الحقل | النوع | إلزامي | ملاحظات |
|---|---|---|---|
email | string | واحد من email/phone/external_user_id | لإزالة التكرار. يُخزّن sha256(lowercase) فقط. |
phone | string | يُخزّن sha256(value) فقط. | |
external_user_id | string ≤ 190 | معرّفك الخاص للمستخدم. مفيد إن لم تجمع email/phone. | |
source | string ≤ 64 | تسمية حرة (slug صفحة، حملة…). | |
country | string (حرفان) | ISO 3166-1 alpha-2. نحوّله لأحرف كبيرة. | |
ip | string | يُخزّن sha256(value) لكشف الاحتيال. لا تُرسله إلا عند الجدوى. | |
meta | object | أي شيء آخر. يُخزّن JSON. | |
nonce | 32-hex string | ✅ | لاستخدام واحد لكل مفتاح، صالح ساعة |
يجب تضمين واحد على الأقل من email أو phone أو external_user_id ليكون لدينا معرّف ثابت لإزالة التكرار.
الاستجابة 202 Accepted
{
"data": { "status": "queued", "kind": "signup", "store_id": 13 },
"meta": { "request_id": "...", "api_version": "v1", "edge": true }
}
202 تعني "قبلناه على الحافة وأدخلناه طابور الكتابة". الكتابة الفعلية في قاعدة البيانات تحدث خلال 5 ثوانٍ عبر طابورنا غير المتزامن. لا تنتظرها. إن احتجت تأكيدًا فوريًا فسجّل webhook لـ signup.counted.
قواعد إزالة التكرار
طبقتان، كلتاهما على مستوى قاعدة البيانات:
(store_id, nonce)UNIQUE — يحمي من إعادة العنادل العرضية لنفس النداء.(store_id, email_hash)UNIQUE — احتساب واحد لكل تاجر لكل بريد، مدى الحياة.
إن صدم تكرار أيًا من القيدين يُدرَج الصف بـ status = 'duplicate' ولا يُحاسَب — usage.signup.billable لا يتغيّر. يمكنك فحص المكررات عبر GET /v1/usage/history.
النتيجة: محاولة تضخيم أرقامك بإعادة إرسال البريد نفسه لن تُجدي. وميزة جانبية: قصة Idempotency لديك تلقائية.
مثال عملي: 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();
}
مثال عملي: 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);
}
مثال عملي: Python (Django signal)
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',
})
الأخطاء
تُعاد على الحافة في ~10 مللي ثانية — تغذية راجعة سريعة للطلبات المشوّهة.
| HTTP | الكود / الرسالة | السبب |
|---|---|---|
| 400 | bad_request "Body must be valid JSON" | الجسم ليس JSON |
| 401 | unauthorized "Missing signature headers" | غاب أحد رؤوس X-DZ-* |
| 401 | unauthorized "Timestamp out of window" | انحراف ساعة > 5 دقائق |
| 401 | unauthorized "Nonce reused" | نفس الـ nonce مرتين خلال ساعة |
| 401 | unauthorized "Signature mismatch" | مدخلات HMAC خاطئة |
| 401 | unauthorized "Invalid or revoked public key" | المفتاح ملغى أو معرّف خاطئ |
| 402 | quota_exceeded | استنفدت حصة الاشتراكات الشهرية |
| 429 | rate_limited | اندفاع في الدقيقة |
أفضل الممارسات
- وقّع على خادمك، لا في المتصفح. لا تُرسل سر التوقيع لمستخدميك.
- ولّد nonces جديدة بـ CSPRNG (
crypto.randomBytes,random.SystemRandom,random_bytesفي PHP). لا تعيد استخدامها أبدًا. - أرسل
external_user_idحتى لو كان لديك email — يصمد لتغيير البريد. - لا تعالج وتُعد المحاولة على أخطاء 401 تلقائيًا — هي دائمة. افحص مرة وأصلح الخطأ.
- أعد المحاولة على 5xx بـ exponential backoff مع nonce جديد في كل مرة.
- اشترك في webhook
signup.countedإن احتجت تأكيد دخول الصف.