# إعداد البيئة و .env

يغطّي هذا الدليل كيفية حمل بيانات اعتماد DZBuild API **بأمان** إلى تطبيقك — تطوير محلي، staging، إنتاج. سواء تبني واجهة Node.js، back-office Laravel/Symfony، خط بيانات Python، خدمة Go صغيرة، أو دالة edge serverless، القواعد ذاتها.

## قبل أن تبدأ[​](#قبل-أن-تبدأ "رابط مباشر إلى قبل أن تبدأ")

* **يجب أن يكون متجرك على خطة Enterprise سارية، وأن يكون مفتاحك مُسجَّلًا في البرنامج التجريبي.** فـ API v1 حصري لخطة Enterprise ومُقيَّد بالبرنامج التجريبي في الإنتاج. على أي خطة أخرى، أو بعد انتهاء اشتراك Enterprise، يُرجع كل نداء `403 forbidden` `"API access requires an active Enterprise plan"`. والمفاتيح المُنشأة من **الإعدادات ← واجهة API** تُسجَّل تلقائيًا؛ أما المفتاح غير المُسجَّل فيُرجع `403 forbidden` `"API is in pilot mode; key not enrolled"` في كل نداء، مهما كان إعدادك صحيحًا.
* **وجّه `DZBUILD_API_BASE` دائمًا إلى `https://api.dzbuild.app/v1`.** فـ `dzbuild.com/api/v1/...` مجرد اسم بديل لنفس الـ API: قد تُحجب عليه بعض المسارات. أما تحديد المعدل لكل متجر (المشترك بين كل مفاتيح المتجر) وإعادة تشغيل idempotency فيسريان على المضيفين معًا.

## ما تحتاج تخزينه[​](#ما-تحتاج-تخزينه "رابط مباشر إلى ما تحتاج تخزينه")

لتكامل واجهة / back-office نموذجي:

| المتغير                  | مثال                                | ملاحظات                                                                                                                                              |
| ------------------------ | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `DZBUILD_API_KEY`        | `dzpk_live_0123456789abcd.<48 hex>` | **رمز bearer الكامل**. **عامله ككلمة مرور.**                                                                                                         |
| `DZBUILD_API_BASE`       | `https://api.dzbuild.app/v1`        | عنوان القاعدة. استخدم هذا المضيف في الإنتاج، لا `dzbuild.com/api/v1` (هو alias فقط).                                                                 |
| `DZBUILD_WEBHOOK_SECRET` | 64 محرفًا ست عشريًا صغيرًا          | سر لكل webhook يعود من التسجيل. بلا بادئة. كل تسليم إلى ذلك الـ webhook يُوقَّع به؛ انظر [أسرار Webhooks](#%D8%A3%D8%B3%D8%B1%D8%A7%D8%B1-webhooks). |

### تشريح رمز الـ bearer[​](#تشريح-رمز-الـ-bearer "رابط مباشر إلى تشريح رمز الـ bearer")

التجار يبترون هذا الرمز باستمرار، فيستحق التوضيح. رمز bearer لمفتاح المنصة هو:

```
dzpk_live_<14 hex>.<48 hex>
```

73 محرفًا، كلها صغيرة بعد البادئة، وبينها **نقطة واحدة**. والجزء `dzpk_live_…` قبل النقطة هو **معرّف المفتاح** — هوية لا بيان اعتماد. و`Authorization: Bearer` يحتاج السلسلة كاملة بمحارفها الـ 73.

وإن أخطأت في ذلك تختلف الطبقتان، وهذا تشخيص مفيد:

* رمز **بلا نقطة إطلاقًا** ← `401 unauthorized "Invalid bearer format"`.
* رمز جيّد الشكل لكنه مجهول / ملغى ← `401 unauthorized "Invalid or revoked API key"`.

لـ **تدفق المفتاح العام** (signups / events من عميل عام):

| المتغير                  | مثال                                  | ملاحظات                                        |
| ------------------------ | ------------------------------------- | ---------------------------------------------- |
| `DZBUILD_PUBLIC_KEY_ID`  | `dzpub_live_...`                      | آمن للشحن في كود العميل (المعرّف عام، السر لا) |
| `DZBUILD_SIGNING_SECRET` | 64 محرفًا ست عشريًا صغيرًا، بلا بادئة | **خادمي فقط** — لتوقيع HMAC لكل جسم طلب        |

## ملفات .env[​](#ملفات-env "رابط مباشر إلى ملفات .env")

النمط الأبسط:

```
# .env (في جذر مشروعك، gitignored)

DZBUILD_API_KEY=dzpk_live_0123456789abcd.0123456789abcdef0123456789abcdef0123456789abcdef

DZBUILD_API_BASE=https://api.dzbuild.app/v1

DZBUILD_WEBHOOK_SECRET=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
```

```
# .gitignore

.env

.env.local

.env.*.local
```

ضع ملف **`.env.example`** بقيم placeholder ليعرف المطورون الآخرون ما يضبطون:

```
# .env.example (ملتزَم بـ git)

DZBUILD_API_KEY=dzpk_live_REPLACE_ME

DZBUILD_API_BASE=https://api.dzbuild.app/v1

DZBUILD_WEBHOOK_SECRET=REPLACE_ME_64_HEX
```

### تحميل .env لكل لغة[​](#تحميل-env-لكل-لغة "رابط مباشر إلى تحميل .env لكل لغة")

#### Node.js / Next.js / Express[​](#nodejs--nextjs--express "رابط مباشر إلى Node.js / Next.js / Express")

```
// next.config.js — Next.js يُحمّل .env و .env.local و .env.production تلقائيًا

// لـ Node عادي:

import 'dotenv/config';

const key = process.env.DZBUILD_API_KEY;
```

#### Python[​](#python "رابط مباشر إلى Python")

```
# pip install python-dotenv

from dotenv import load_dotenv

import os

load_dotenv()

key = os.environ['DZBUILD_API_KEY']
```

#### PHP / Laravel[​](#php--laravel "رابط مباشر إلى PHP / Laravel")

```
// Laravel يُحمّل .env تلقائيًا

$key = env('DZBUILD_API_KEY');



// PHP عادي:

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);

$dotenv->load();

$key = $_ENV['DZBUILD_API_KEY'];
```

#### Go[​](#go "رابط مباشر إلى Go")

```
// go get github.com/joho/godotenv

godotenv.Load()

key := os.Getenv("DZBUILD_API_KEY")
```

#### Flutter / موبايل[​](#flutter--موبايل "رابط مباشر إلى Flutter / موبايل")

**لا تشحن مفتاح المنصة في تطبيقك المحمول.** بدلًا من ذلك:

1. ينادي تطبيقك المحمول **خادمك الخلفي**.
2. خادمك الخلفي يحمل المفتاح ويُمرّر الطلبات إلى DZBuild.

إن احتاج تطبيقك المحمول إلى تسجيل `signups`، فأرسلها هي أيضًا إلى **خادمك الخلفي**. فكل طلب بالمفتاح العام يحمل توقيع HMAC مصنوعًا بسر التوقيع، وهذا السر لا يُشحن أبدًا داخل تطبيق: خادمك الخلفي يوقّع النداء ويُمرّره، كما في قسم المفتاح العام أدناه. ومعرّف المفتاح العام وحده آمن للكشف. انظر [نقاط المفتاح العام](https://dzbuild.com/ar/ar/api-docs/public-key/signups.md).

## بيئات الإنتاج[​](#بيئات-الإنتاج "رابط مباشر إلى بيئات الإنتاج")

في الإنتاج **لا تستعمل ملف .env**. استعمل مدير الأسرار الأصلي للمنصة:

### Vercel / Netlify / Cloudflare Pages[​](#vercel--netlify--cloudflare-pages "رابط مباشر إلى Vercel / Netlify / Cloudflare Pages")

```
المشروع → الإعدادات → Environment Variables

   DZBUILD_API_KEY = dzpk_live_...

   DZBUILD_API_BASE = https://api.dzbuild.app/v1

   DZBUILD_WEBHOOK_SECRET = <64-char lowercase hex>
```

علّم متغيرات الإنتاج في scope "Production". متغيرات staging في "Preview" أو بيئة منفصلة.

### Cloudflare Workers[​](#cloudflare-workers "رابط مباشر إلى Cloudflare Workers")

```
wrangler secret put DZBUILD_API_KEY

# (الصق القيمة عند الطلب)
```

متاح في الـ worker كـ `env.DZBUILD_API_KEY` (مع `[vars]` في `wrangler.toml`).

### AWS Lambda / API Gateway[​](#aws-lambda--api-gateway "رابط مباشر إلى AWS Lambda / API Gateway")

استعمل **AWS Secrets Manager** أو **Parameter Store**:

```
import boto3, json

secret = json.loads(

    boto3.client('secretsmanager').get_secret_value(SecretId='dzbuild/prod')['SecretString']

)

key = secret['DZBUILD_API_KEY']
```

لا تخزّن المفتاح في Lambda env vars بنص واضح (يظهر في سجلات CloudTrail). أحل من Secrets Manager.

### Docker / Docker Compose[​](#docker--docker-compose "رابط مباشر إلى Docker / Docker Compose")

```
# docker-compose.yml

services:

  app:

    image: yourapp:latest

    env_file:

      - .env.production    # غير ملتزَم

    environment:

      - NODE_ENV=production
```

لـ Kubernetes استعمل `Secret`:

```
apiVersion: v1

kind: Secret

metadata:

  name: dzbuild-creds

type: Opaque

stringData:

  DZBUILD_API_KEY: dzpk_live_...

  DZBUILD_WEBHOOK_SECRET: <64-char lowercase hex>
```

ثم أحل في النشر:

```
envFrom:

  - secretRef:

      name: dzbuild-creds
```

### GitHub Actions[​](#github-actions "رابط مباشر إلى GitHub Actions")

```
# .github/workflows/deploy.yml

env:

  DZBUILD_API_KEY: ${{ secrets.DZBUILD_API_KEY }}
```

اضبط السر في **Repo → Settings → Secrets and variables → Actions**.

## مفاتيح dev مقابل production[​](#مفاتيح-dev-مقابل-production "رابط مباشر إلى مفاتيح dev مقابل production")

**أنشئ دائمًا مفتاحين منفصلين:**

* واحد بالاسم `dev` أو `staging` — للاستخدام في `.env.local` / بيئات dev
* واحد بالاسم `production` — يُستعمل فقط في نشر الإنتاج الفعلي

إن تسرّب مفتاح dev، تحرق dev فقط. بيانات الإنتاج تبقى سليمة.

### كيف تحصل على المفتاح[​](#كيف-تحصل-على-المفتاح "رابط مباشر إلى كيف تحصل على المفتاح")

يُنشئ مالك المتجر المفاتيح من لوحة تحكم التاجر عبر **الإعدادات ← واجهة API** (`/dashboard/api`). ويجب أن يكون المتجر على خطة Enterprise سارية، ويحتفظ بـ 3 مفاتيح نشطة كحد أقصى.

1. **مفتاحك الأول** يأتي من تلك الصفحة: اضغط **إنشاء مفتاح API**، وسمِّه باسم البيئة التي سيعيش فيها (`myapp-dev`، `myapp-prod`)، ثم انسخ رمز الـ bearer الذي يظهر مرة واحدة فقط. والمفاتيح المُنشأة هناك مُسجَّلة مسبقًا في البرنامج التجريبي.

2. **المفاتيح التالية** يمكنك إنشاؤها بنفسك انطلاقًا من مفتاح قائم:

   ```
   curl -X POST 'https://api.dzbuild.app/v1/keys' \

     -H "Authorization: Bearer $DZBUILD_API_KEY" \

     -H "Content-Type: application/json" \

     -H "Idempotency-Key: $(uuidgen)" \

     -d '{"type":"platform","name":"myapp-dev"}'

   # HTTP 200 → { "data": { "key_id", "bearer_token", "signing_secret", "note" } }
   ```

   يرث المفتاح الجديد فئة تحديد المعدل وعَلَم البرنامج التجريبي من المفتاح المنادي حرفيًا.

**الصلاحيات غير قابلة للاختيار.** فـ `POST /v1/keys` لا يقبل إلا `type` و `name`. ومفتاح المنصة المُنشأ من **الإعدادات ← واجهة API** يحصل على المجموعة الافتراضية الكاملة: `store:read` و `store:write` و `products:read` و `products:write` و `orders:read` و `orders:write` و `customers:read` و `landing_pages:read` و `landing_pages:write` و `promos:read` و `promos:write` و `pixels:read` و `pixels:write` و `shipping:read` و `shipping:write` و `webhooks:read` و `webhooks:write` و `usage:read` و `analytics:read` و `whatsapp:read` و `whatsapp:send`. ومفتاح المنصة المُنشأ بـ `POST /v1/keys` يحصل على هذه المجموعة ناقصًا كل صلاحية لا يملكها المفتاح المنادي، ولا يحصل أبدًا على أكثر منها. أما المفتاح العام فيحصل دائمًا على `signups:write` و `events:write`. والفصل بين dev و prod يأتي من استعمال مفتاحين مختلفين، لا من تضييق الصلاحيات.

## أسرار Webhooks[​](#أسرار-webhooks "رابط مباشر إلى أسرار Webhooks")

عند تسجيل webhook تتضمن الاستجابة `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"]

  }'
```

الاستجابة — HTTP **200**، وثلاثة حقول فقط:

```
{

  "data": {

    "id":     42,

    "secret": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",

    "note":   "Save the secret now — it is not retrievable after this response."

  },

  "meta": { "request_id": "...", "api_version": "v1" }

}
```

الـ `secret` سلسلة مجرّدة من 64 محرفًا ست عشريًا صغيرًا — لا توجد بادئة `dzwh_sec_`. وهو يظهر **مرة واحدة**؛ احفظه فورًا في مدير أسرارك. إن فقدته، احذف الـ webhook وسجّل واحدًا جديدًا.

تحقّق من كل تسليم API v1 بهذا السر

يحمل كل تسليم إلى الـ webhook الترويسة `X-DZ-Signature: t=<unix seconds>,v1=<hex>`، وهي HMAC-SHA256 للقيمة `t + "." + raw_body` بالمفتاح `DZBUILD_WEBHOOK_SECRET`. أعد حسابها، وقارن بزمن ثابت، وارفض أي `t` تبتعد عن ساعتك بأكثر من 300 ثانية. أما الـ webhook المسجّل قبل اعتماد التوقيع بسر كل webhook فيبقى على توقيع قديم لا يستطيع هذا السر فحصه: سجّل ذلك الـ webhook من جديد.

الوصفة والشيفرة بأربع لغات موجودة في [التحقق من التواقيع](https://dzbuild.com/ar/ar/api-docs/webhooks/verifying-signatures.md).

## تدفق المفتاح العام (signups / events)[​](#تدفق-المفتاح-العام-signups--events "رابط مباشر إلى تدفق المفتاح العام (signups / events)")

يوجد تدفق المفتاح العام للنقاط محدودة النطاق (تتبع signups، تتبع events) حيث لا تريد إدخال مفتاح منصة في الحلقة. و**معرّف المفتاح العام** وحده غير سرّي — أما **سر التوقيع** فيبقى على خادمك الخلفي.

لا تنادِ `/v1/signups` مباشرة من متصفح

ثلاثة أمور تُفشل النداء المباشر من المتصفح اليوم:

* **لا يوجد CORS.** لا تجيب الـ API على أي طلب تمهيدي (preflight): طلب `OPTIONS` بلا مفتاح يتلقى الرد المعتاد `401` دون أي ترويسة `Access-Control-*`، فيحجب المتصفح النداء. الـ API مخصّصة للنداءات من خادم إلى خادم فقط.
* **`Idempotency-Key` إلزامية** في كل POST، ومن السهل نسيانها في كود العميل.
* **`crypto.randomUUID()` ليس nonce صالحًا.** فالـ nonce يجب أن يكون 32 محرفًا ست عشريًا صغيرًا بالضبط؛ أما UUID بـ 36 محرفًا وشرطات فيُرجع `401 unauthorized "Invalid nonce format"`.

مرّر عبر خادمك الخلفي بدلًا من ذلك — بالنمط أدناه.

```
// المتصفح: تحدّث مع نقطتك أنت، لا مع api.dzbuild.app

await fetch('/api/track-signup', {

  method: 'POST',

  headers: { 'Content-Type': 'application/json' },

  body: JSON.stringify({ email, external_user_id: externalId })

});
```

```
// خادمك الخلفي (Node): يحمل سر التوقيع، يوقّع ويُمرّر.

import crypto from 'node:crypto';



const KEY_ID = process.env.DZBUILD_PUBLIC_KEY_ID;

const SECRET = process.env.DZBUILD_SIGNING_SECRET;



export async function trackSignup({ email, external_user_id }) {

  const nonce = crypto.randomBytes(16).toString('hex');   // 32 محرفًا ست عشريًا صغيرًا

  const ts    = Math.floor(Date.now() / 1000).toString();

  const body  = JSON.stringify({ email, external_user_id, source: 'web', nonce });

  const hash  = crypto.createHash('sha256').update(body).digest('hex');

  const sig   = crypto.createHmac('sha256', SECRET)

    .update(`${KEY_ID}\n${nonce}\n${ts}\n${hash}`).digest('hex');



  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,

      'Idempotency-Key': nonce,

      'Content-Type':    'application/json'

    },

    body

  });

}
```

سر التوقيع لا يغادر خادمك أبدًا. معرّف المفتاح العام يمكن لأي أحد فحصه — وهذا بقصد. انظر [الاشتراكات](https://dzbuild.com/ar/ar/api-docs/public-key/signups.md) للعقد الكامل.

## نفق dev محلي للـ webhooks[​](#نفق-dev-محلي-للـ-webhooks "رابط مباشر إلى نفق dev محلي للـ webhooks")

تحتاج webhooks DZBuild URL HTTPS عام. للاختبار محليًا استعمل tunnel:

```
# ngrok

ngrok http 3000



# Cloudflare Tunnel

cloudflared tunnel --url http://localhost:3000
```

سجّل URL الـ tunnel كهدف webhook. ولا يمكن تعديل URL الـ webhook بعد تسجيله، فإن تغيّر URL الـ tunnel احذف الـ webhook وسجّل الـ URL الجديد واحفظ السر الجديد الذي يُرجعه التسجيل (ngrok مجاني يغير URL في كل مرة؛ ادفع 8$ شهريًا لـ subdomain ثابت).

## سياسة التدوير[​](#سياسة-التدوير "رابط مباشر إلى سياسة التدوير")

دوّر المفاتيح:

* **ربع سنوي** لمفاتيح الإنتاج (ضع تذكير في التقويم)
* **فورًا** إن احتمل أن مفتاحًا تسرّب (التزم بـ git، صورة شاشة، أُرسل في chat)
* **عند مغادرة عضو فريق** إن كان لديه وصول لمدير الأسرار

إجراء التدوير:

1. أنشئ مفتاحًا جديدًا بـ `POST /v1/keys` (انظر [كيف تحصل على المفتاح](#%D9%83%D9%8A%D9%81-%D8%AA%D8%AD%D8%B5%D9%84-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D9%85%D9%81%D8%AA%D8%A7%D8%AD)). سيرث فئة المفتاح القديم وعَلَم البرنامج التجريبي. ويحتفظ المتجر بـ 3 مفاتيح نشطة كحد أقصى: عند بلوغ الحد يُجيب النداء بـ `400 bad_request` `"Key limit reached for this store (3 active keys). Revoke unused keys first."`، فألغِ مفتاحًا غير مستعمل قبل التدوير.
2. حدّث مدير الأسرار / متغيرات البيئة إلى المفتاح الجديد.
3. انشر. وتحقّق من أن المفتاح الجديد قيد الاستعمال: `GET /v1/keys` يسرد كل مفتاح مع `last_used_at` الخاص به (أما `GET /v1/usage` فيعدّ المتجر كله، لا مفتاحًا واحدًا).
4. بعد تأكيد 24 ساعة من حركة نظيفة على الجديد، **ألغِ القديم**: `DELETE /v1/keys/{old_key_id}`.

## أخطاء شائعة[​](#أخطاء-شائعة "رابط مباشر إلى أخطاء شائعة")

| الخطأ                              | ما يحدث                                         | الإصلاح                                                 |
| ---------------------------------- | ----------------------------------------------- | ------------------------------------------------------- |
| التزام `.env` في git               | المفتاح صار في تاريخ git إلى الأبد؛ دوّره فورًا | `git filter-repo` لا يُصلح forks/clones؛ اعتبره متسربًا |
| استخدام مفتاح المنصة في كود متصفح  | العملاء يقرؤون network tab → يرونه ويسرقونه     | انقله إلى back-end / serverless؛ دوّره                  |
| Hardcode `dzpk_live_...` في source | نفس الأمر                                       | استعمل env vars؛ دوّر                                   |
| نفس المفتاح لـ dev وprod           | خطأ dev يضرب بيانات prod                        | أنشئ مفتاحين؛ لا تشارك أبدًا                            |
| فقدان سر webhook                   | لا يمكن التحقق من التسليمات                     | احذف الـ webhook، سجّل غيره                             |
| تسجيل مفتاح API في سجلات التطبيق   | مدققون / شاحنو سجلات / كل من له وصول للسجل يراه | redact secrets في إعدادات logger؛ دوّر                  |

## قائمة سريعة قبل الإطلاق[​](#قائمة-سريعة-قبل-الإطلاق "رابط مباشر إلى قائمة سريعة قبل الإطلاق")

* [ ] `.env` في `.gitignore` (ولم يُلتزَم سهوًا)
* <!-- -->
  مفتاح الإنتاج اسمه `prod` ويُستعمل فقط في prod
* <!-- -->
  مفتاح الـ dev اسمه `dev` ويُستعمل فقط في dev/staging
* <!-- -->
  أسرار webhooks في مدير أسرار، ليس في الكود
* <!-- -->
  رأس `Authorization` على **كل** نداء API من خادمك الخلفي
* <!-- -->
  كود المتصفح لا يرى `dzpk_live_*` (فقط `dzpub_live_*` إن استعملت تدفق المفتاح العام)
* <!-- -->
  نقطة webhook لديك تتحقق من `X-DZ-Signature` بسر الـ webhook، وتزيل التكرار بـ `delivery_id` الموجود في الجسم
* <!-- -->
  لديك تذكير تدوير مفاتيح في تقويمك
* <!-- -->
  لديك logging لا يلتقط أجسام الطلبات الكاملة (قد تتضمن مفاتيح API في رؤوس العميل)
