إنتقل إلى المحتوى الرئيسي

POST /v1/signups

هذه هي الوحدة الرئيسية القابلة للقياس في DZBuild API. كل اشتراك يصل عبرها يُحسب ضمن الحصة الشهرية لخطتك ويسهم في تسعير المنصة لتكاملات الشركاء.

مصممة لحالة استخدام محددة: لديك موقع/تطبيق خارجي يقبل اشتراكات المستخدمين، وتريد أن يُحتسب كل واحد منها لحساب التاجر في DZBuild. أمثلة:

  • موقع WordPress للتسويق → يملأ المستخدم نموذج التسجيل → تنادي /v1/signups.
  • تطبيق جوال يسجّل المستخدمون فيه → الباك إند يُنادي /v1/signups.
  • صفحة هبوط على نطاق آخر → الباك إند يُنادي /v1/signups.

ليست مصممة لطلبات الواجهة (تلك تُنشئ عملاء عبر تدفق الواجهة نفسه) ولا لاشتراكات lead-magnet المعزولة (استخدم /v1/events لها).

المصادقة

مفتاح عام + HMAC. خادمك الخلفي يوقّع كل نداء. انظر المصادقة لمخطط HMAC الكامل.

الجسم

{
"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"
}
الحقلالنوعإلزاميملاحظات
emailstringواحد من email/phone/external_user_idلإزالة التكرار. يُخزّن sha256(lowercase) فقط.
phonestringيُخزّن sha256(value) فقط.
external_user_idstring ≤ 190معرّفك الخاص للمستخدم. مفيد إن لم تجمع email/phone.
sourcestring ≤ 64تسمية حرة (slug صفحة، حملة…).
countrystring (حرفان)ISO 3166-1 alpha-2. نحوّله لأحرف كبيرة.
ipstringيُخزّن sha256(value) لكشف الاحتيال. لا تُرسله إلا عند الجدوى.
metaobjectأي شيء آخر. يُخزّن JSON.
nonce32-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.

قواعد إزالة التكرار

طبقتان، كلتاهما على مستوى قاعدة البيانات:

  1. (store_id, nonce) UNIQUE — يحمي من إعادة العنادل العرضية لنفس النداء.
  2. (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الكود / الرسالةالسبب
400bad_request "Body must be valid JSON"الجسم ليس JSON
401unauthorized "Missing signature headers"غاب أحد رؤوس X-DZ-*
401unauthorized "Timestamp out of window"انحراف ساعة > 5 دقائق
401unauthorized "Nonce reused"نفس الـ nonce مرتين خلال ساعة
401unauthorized "Signature mismatch"مدخلات HMAC خاطئة
401unauthorized "Invalid or revoked public key"المفتاح ملغى أو معرّف خاطئ
402quota_exceededاستنفدت حصة الاشتراكات الشهرية
429rate_limitedاندفاع في الدقيقة

أفضل الممارسات

  • وقّع على خادمك، لا في المتصفح. لا تُرسل سر التوقيع لمستخدميك.
  • ولّد nonces جديدة بـ CSPRNG (crypto.randomBytes, random.SystemRandom, random_bytes في PHP). لا تعيد استخدامها أبدًا.
  • أرسل external_user_id حتى لو كان لديك email — يصمد لتغيير البريد.
  • لا تعالج وتُعد المحاولة على أخطاء 401 تلقائيًا — هي دائمة. افحص مرة وأصلح الخطأ.
  • أعد المحاولة على 5xx بـ exponential backoff مع nonce جديد في كل مرة.
  • اشترك في webhook signup.counted إن احتجت تأكيد دخول الصف.