المطورون/البداية
المصادقة
يحمل كل استدعاء لـ API معرّف المفتاح (key ID) وتوقيعًا. يثبت التوقيع أن الاستدعاء صادر منك، ولا يستطيع أحد تعديله أو تكراره. أما سرّ التوقيع (signing secret) نفسه فلا يُرسل أبدًا.
ذات صلة: المنتجات والطلبات · الأمان · البداية
#بيانات الاعتماد
| البيان | مثال | هل هو سري؟ |
|---|---|---|
| key ID | ck_live_... | لا. يحدد المفتاح الذي تستخدمه. |
| signing secret | cs_live_... | نعم. توقّع به، ولا يغادر خادمك أبدًا. |
| Merchant ID | M00000001 | لا. اختياري في استدعاءات API. |
- ينشئ صاحب الحساب (Owner) المفاتيح من البوابة (راجع مفاتيح API).
- تعرض البوابة سرّ التوقيع مرة واحدة فقط عند إنشاء المفتاح. لا يستطيع أحد عرضه مرة أخرى، ولا حتى CardV.
- تبدأ مفاتيح Live بـ
ck_live_وcs_live_، وتبدأ مفاتيح Sandbox بـck_test_وcs_test_. ولا يعمل مفتاح إحدى البيئتين في الأخرى. - لا تعمل الواجهة إلا بعد موافقة CardV على شركتك.
عندها يُظهر
GET/accountالقيمة"api_access_enabled": true.
#الترويسات
أرسل هذه الترويسات الأربع مع كل استدعاء، بما في ذلك استدعاءات GET:
X-Key-Id: ck_live_xxxxxxxxxxxxxxxxxxxxxxxx
X-Timestamp: 1790000000
X-Nonce: 0123456789abcdef0123456789abcdef
X-Signature: e6e45d09ed271d8d721701b560d56a6f5104fd8a52aaad5380166f432bba6e89X-Key-Idهو المعرّف العام لمفتاحك.X-Timestampهو الوقت الحالي بتوقيت Unix بالثواني. يجب ألا يبعد عن ساعة CardV أكثر من 5 دقائق (300 ثانية).X-Nonceقيمة عشوائية لا تستخدمها أكثر من مرة. استخدم 32 حرفًا عشوائيًا بالنظام الست عشري (hex).X-Signatureمشروح في توقيع الطلب، ويجب أن يكون hex بأحرف صغيرة.
يمكنك أيضًا إرسال X-Merchant-Id. وإن أرسلته، فيجب أن يطابق المفتاح.
#توقيع الطلب
حوّل محتوى طلبك إلى بايتات مرة واحدة فقط. ستوقّع هذه البايتات نفسها وترسلها كما هي. استدعاء
GETمحتواه فارغ.احسب بصمة المحتوى:
BODY_HASH= قيمة SHA-256 للمحتوى، بصيغة hex بأحرف صغيرة. للمحتوى الفارغ تكون القيمةe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.اجمع هذه الأسطر الخمسة مع فاصل سطر (
\n) بينها، دون فاصل سطر في النهاية:<METHOD> <PATH with query string> <X-Timestamp> <X-Nonce> <BODY_HASH>يُكتب
METHODبأحرف كبيرة، مثلGETأوPOST. يتضمن المسار سلسلة الاستعلام (query string) تمامًا كما ترسلها، مثل/api/v1/skus?limit=50.وقّع النص:
X-Signature= قيمة HMAC-SHA256 لهذا النص، باستخدام سرّ التوقيع كمفتاح. اكتب النتيجة بصيغة hex بأحرف صغيرة.
تُوقَّع استدعاءات شحن رصيد الهاتف المحمول بالطريقة نفسها، مع استخدام مسار كل منها، مثل /api/v1/recharge/orders.
الخطأ الأكثر شيوعًا هو توقيع نسخة من JSON ثم إرسال نسخة أخرى.
فمثلًا {"a":1} و{"a": 1} بايتات مختلفة.
تأكد من أن مكتبة HTTP لديك لا تغيّر المحتوى ولا تعيد ترتيب سلسلة الاستعلام بعد التوقيع.
قيم اختبار. تحقق من برنامجك بهذه القيم. السرّ هنا وهمي.
Signing secret cs_test_TEST_ONLY_not_a_real_secret_0123456789abcdef
X-Timestamp 1790000000
X-Nonce 0123456789abcdef0123456789abcdefاستدعاء GET:
Method GET
Path /api/v1/skus?limit=50
BODY_HASH e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
X-Signature 58f0362c355a6624fff9f0b84d47591c570263908832294375b7bf27731628e5استدعاء POST إلى /api/v1/orders بهذا المحتوى (106 بايت، في سطر واحد، دون فاصل سطر في النهاية):
{"external_order_id":"TEST-0001","items":[{"sku_id":"S000001","quantity":1,"expected_unit_price":"9.25"}]}BODY_HASH 5814ffcdadfa9eb69348150f9fd6895c00b4c37cf6cd0202b83174354645023e
X-Signature e6e45d09ed271d8d721701b560d56a6f5104fd8a52aaad5380166f432bba6e89#أمثلة برمجية
تعطي كل الأمثلة نتائج قيم الاختبار نفسها. حمّل السرّ من مخزن الأسرار، لا من الشيفرة.
cURL (bash + OpenSSL)
BASE_URL="https://sandbox.cardv.net"
REQ_PATH="/api/v1/orders" # do not call this variable PATH
BODY='{"external_order_id":"SHOP-10001","items":'
BODY+='[{"sku_id":"S000001","quantity":1,"expected_unit_price":"9.25"}]}'
TS=$(date +%s)
NONCE=$(openssl rand -hex 16)
BODY_HASH=$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | sed 's/^.*= //')
SIG=$(printf 'POST\n%s\n%s\n%s\n%s' "$REQ_PATH" "$TS" "$NONCE" "$BODY_HASH" \
| openssl dgst -sha256 -hmac "$CARDV_SIGNING_SECRET" -hex | sed 's/^.*= //')
curl -sS -X POST "$BASE_URL$REQ_PATH" \
-H "Content-Type: application/json" \
-H "X-Key-Id: $CARDV_KEY_ID" \
-H "X-Timestamp: $TS" \
-H "X-Nonce: $NONCE" \
-H "X-Signature: $SIG" \
--data-raw "$BODY"استخدم --data-raw وليس -d @file، لأن -d يحذف فواصل الأسطر فلا تطابق البايتات المرسلة ما وقّعته.
في استدعاء GET، وقّع GET والمسار مع سلسلة الاستعلام وبصمة المحتوى الفارغ، ولا ترسل أي محتوى.
Python (requests)
import hashlib, hmac, json, os, secrets, time
import requests
BASE_URL = "https://sandbox.cardv.net"
KEY_ID = os.environ["CARDV_KEY_ID"]
SIGNING_SECRET = os.environ["CARDV_SIGNING_SECRET"]
def signed_headers(method: str, path: str, body: bytes) -> dict:
ts, nonce = str(int(time.time())), secrets.token_hex(16)
body_hash = hashlib.sha256(body).hexdigest()
text = "\n".join([method, path, ts, nonce, body_hash])
signature = hmac.new(SIGNING_SECRET.encode(), text.encode(), hashlib.sha256).hexdigest()
return {"X-Key-Id": KEY_ID, "X-Timestamp": ts, "X-Nonce": nonce, "X-Signature": signature}
def cardv_get(path: str) -> requests.Response:
# Put the query string in `path` so the signed path and the sent path are identical.
return requests.get(BASE_URL + path, headers=signed_headers("GET", path, b""), timeout=30)
def cardv_post(path: str, payload: dict) -> requests.Response:
body = json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode()
headers = {**signed_headers("POST", path, body), "Content-Type": "application/json"}
return requests.post(BASE_URL + path, data=body, headers=headers, timeout=30)
resp = cardv_post("/api/v1/orders", {
"external_order_id": "SHOP-10001",
"items": [{"sku_id": "S000001", "quantity": 1, "expected_unit_price": "9.25"}],
})
print(resp.status_code, resp.json())
order_id = resp.json()["order"]["order_id"]
print(cardv_get(f"/api/v1/orders/{order_id}").json()["status"])Node.js 18+ (built-in fetch)
import crypto from "node:crypto";
const BASE_URL = "https://sandbox.cardv.net";
const { CARDV_KEY_ID, CARDV_SIGNING_SECRET } = process.env;
function signedHeaders(method, path, body) {
const timestamp = String(Math.floor(Date.now() / 1000));
const nonce = crypto.randomBytes(16).toString("hex");
const bodyHash = crypto.createHash("sha256").update(body).digest("hex");
const text = [method, path, timestamp, nonce, bodyHash].join("\n");
const signature = crypto.createHmac("sha256", CARDV_SIGNING_SECRET).update(text, "utf8").digest("hex");
return { "X-Key-Id": CARDV_KEY_ID, "X-Timestamp": timestamp, "X-Nonce": nonce, "X-Signature": signature };
}
async function cardvGet(path) {
const res = await fetch(BASE_URL + path, { headers: signedHeaders("GET", path, Buffer.alloc(0)) });
return { status: res.status, body: await res.json() };
}
async function cardvPost(path, payload) {
const body = Buffer.from(JSON.stringify(payload), "utf8"); // serialize once
const headers = { ...signedHeaders("POST", path, body), "Content-Type": "application/json" };
const res = await fetch(BASE_URL + path, { method: "POST", headers, body });
return { status: res.status, body: await res.json() };
}
console.log(await cardvPost("/api/v1/orders", {
external_order_id: "SHOP-10001",
items: [{ sku_id: "S000001", quantity: 1, expected_unit_price: "9.25" }],
}));
console.log(await cardvGet("/api/v1/balance"));PHP (التوقيع فقط)
<?php
function cardv_sign(
string $secret, string $method, string $path, string $body, string $ts, string $nonce
): string {
$text = implode("\n", [$method, $path, $ts, $nonce, hash('sha256', $body)]);
return hash_hmac('sha256', $text, $secret); // lowercase hex
}
// Send exactly $body. For a GET call use $body = ''.
$body = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$ts = (string) time();
$nonce = bin2hex(random_bytes(16));
$signature = cardv_sign($signingSecret, 'POST', '/api/v1/orders', $body, $ts, $nonce);
// Headers: X-Key-Id, X-Timestamp, X-Nonce, X-Signature#الأخطاء
تُرجع كل هذه الأخطاء HTTP 403 (وليس 401)، باستثناء تجاوز حد الطلبات الذي يُرجع 429. ويبدو المحتوى هكذا:
{"detail": "Invalid HMAC signature."}| المشكلة | ما الذي تفعله |
|---|---|
| key ID غير معروف أو معطّل | راجع X-Key-Id والبيئة (Live أو Sandbox). |
| الوصول إلى API غير مفعّل | انتظر موافقة CardV على شركتك. |
| عنوان IP لخادمك غير مسموح به | أضفه إلى قائمة عناوين IP المسموح بها في البوابة. |
| نقطة النهاية متاحة في البوابة فقط | استخدم البوابة. ست عشرة نقطة نهاية فقط تعمل بالمفتاح. |
| التوقيع مفقود أو غير صحيح | أصلح شيفرة التوقيع، واختبرها بقيم الاختبار. |
| الطابع الزمني قديم جدًا أو في المستقبل | زامن ساعة خادمك (NTP). |
| قيمة nonce مستخدمة من قبل | استخدم قيمة nonce عشوائية جديدة مع كل طلب. |
| طلبات كثيرة جدًا (429) | انتظر عدد الثواني المذكور في Retry-After، ثم أعد المحاولة. |
مهم:
- عند إعادة إرسال طلب، أنشئ طابعًا زمنيًا وقيمة nonce وتوقيعًا جديدة. وأبقِ المحتوى كما هو. راجع إعادة المحاولة بأمان.
- تُعدّ قيمة nonce مستخدمة حتى لو كان التوقيع خاطئًا.
- تسجّل CardV المحاولات الفاشلة، وتنبّه فريقها إذا كثرت.
#قائمة عناوين IP المسموح بها
تضمن هذه القائمة ألا يستخدم مفتاحك إلا خوادمك. يديرها صاحب الحساب (Owner) من البوابة.
- القائمة اختيارية. إن لم تضف أي قاعدة، تُقبل الاستدعاءات من أي عنوان IP.
- بمجرد إضافة قاعدة واحدة، تحصل الاستدعاءات من العناوين الأخرى على HTTP 403.
- تُكتب القواعد هكذا:
203.0.113.10/32(IPv4) أو2001:db8::/48(IPv6). - تنطبق القائمة على استدعاءات مفتاح API فقط، لا على تسجيل الدخول إلى البوابة.
- أضف كل عناوين خوادمك قبل تفعيلها، بما فيها بوابات NAT والمناطق الاحتياطية.
#مفاتيح API
يستطيع المفتاح استخدام نقاط النهاية الست عشرة الواردة في نظرة سريعة على الواجهة فقط. ومن بينها إرسال الطلبات وقراءة الأكواد، لذا احمِ سرّ التوقيع كما تحمي كلمة مرور صاحب الحساب.
صاحب الحساب (Owner) وحده يدير المفاتيح، من البوابة عبر Integrations → API keys. كرّر ذلك بشكل منفصل في Live وفي Sandbox.
- أنشئ مفتاحًا وسمِّه. تعرض البوابة key ID وسرّ التوقيع. انسخ السرّ إلى مخزن الأسرار الآن. فهو لا يُعرض إلا هذه المرة.
- أعِد إنشاء السرّ إذا فقدته. يبقى key ID كما هو. يتوقف السرّ القديم عن العمل فورًا، لذا انشر السرّ الجديد على كل خوادمك على الفور.
- عطّل المفتاح عندما لا تعود بحاجة إليه. يسري التعطيل فورًا ولا يمكن التراجع عنه. بعد ذلك يمكنك إزالة المفتاح المعطّل من القائمة. تحتفظ CardV بسجلّه.
لتغيير المفاتيح دون توقف الخدمة:
- أنشئ مفتاحًا جديدًا واحفظ سرّه.
- انشره على كل خوادمك.
- تأكد من البوابة أن المفتاح القديم لم يعد مستخدمًا.
- عطّل المفتاح القديم.
غيّر المفاتيح مرة واحدة سنويًا على الأقل، وكلما غادر شخص كان يستطيع الوصول إليها. وإذا احتمل تسرّب مفتاح، فعطّله أولًا ثم ابحث في الأمر.
#المفاتيح القديمة
المفاتيح التي أُنشئت قبل أكتوبر 2026 تبدأ بـ cvb2b_ وتستخدم الترويستين القديمتين
X-Merchant-Id وX-Api-Key، مع توقيع على استدعاءات POST فقط.
يُرسل المفتاح القديم مع كل طلب، لذا يسهل تسرّبه.
لم تعد بيئتا Live وSandbox تقبلان هذه المفاتيح. انتقل الآن إلى مفتاح جديد: أنشئ مفتاحًا في البوابة، وحوّل شيفرتك إلى الترويسات المذكورة أعلاه، ثم عطّل المفتاح القديم.
هل لديك سؤال حول التكامل؟ راسلنا على [email protected] مع ذكر Merchant ID ومعرّف الطلب أو الاستدعاء.