التوثيق

تكامل المواقع - من الخادم

أضف التحقق بالمكالمة الفائتة إلى أي موقع أو CRM: أنشئ الجلسات من خادمك، اعرض رقم البوابة، تابع الحالة أو استقبل Webhooks، ولا تكشف مفتاحك السري أبدًا.

المفاتيح السرية تبقى على خادمك

لا تضع sk_live_… أو sk_test_… في JavaScript المتصفح أو تطبيق الهاتف أو مستودع عام. المتصفح يتحدث مع خادمك فقط، وخادمك هو من يستدعي Shernova بالمفتاح السري.

التدفق الكامل

المستخدم لا يثبّت أي تطبيق. خادمك ينشئ الجلسة، وصفحتك تعرض رقم البوابة، والمكالمة نفسها تثبت الملكية. لا يُعتبر الرقم موثّقًا إلا عندما يكون статус الجلسة القادم من API (أو من Webhook موقّع) هو verified أو completed.

1. إنشاء جلسة

أرسل رقم الهاتف بصيغة E.164 مع client_request_id ثابت حتى تكون إعادة المحاولة آمنة (idempotent). ضع المفتاح السري في متغير بيئة على الخادم.

Node.js 20+ (بدون مكتبات)
const API = 'https://api.shernova.com';

export async function createVerification(phoneNumber, requestId) {
  const res = await fetch(API + '/v1/verifications', {
    method: 'POST',
    headers: {
      Authorization: 'Bearer ' + process.env.SHERNOVA_SECRET_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      phone_number: phoneNumber,
      client_request_id: requestId,
    }),
  });
  const data = await res.json();
  if (!res.ok) throw Object.assign(new Error(data.message), { data });
  return data;
}
Python (requests)
import os, requests

def create_verification(phone_number: str, request_id: str) -> dict:
    response = requests.post(
        "https://api.shernova.com/v1/verifications",
        headers={"Authorization": f"Bearer {os.environ['SHERNOVA_SECRET_KEY']}"},
        json={"phone_number": phone_number, "client_request_id": request_id},
        timeout=20,
    )
    response.raise_for_status()
    return response.json()
PHP (cURL)
<?php
function create_verification(string $phone, string $requestId): array {
    $ch = curl_init('https://api.shernova.com/v1/verifications');
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => [
            'Authorization: Bearer ' . getenv('SHERNOVA_SECRET_KEY'),
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS => json_encode([
            'phone_number' => $phone,
            'client_request_id' => $requestId,
        ]),
    ]);
    $body = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    if ($status >= 400) throw new RuntimeException((string) $body);
    return json_decode((string) $body, true);
}

2. التعامل الصحيح مع queued وwaiting

يكون gateway_phone_number مساويًا لـ null أثناء queued لأن خط التحقق غير متاح بعد. لا تعرض رقمًا وهميًا ولا تطلب من المستخدم الاتصال قبل أن يصبح status === "waiting". مثال استجابة جلسة جاهزة:

جلسة waiting
{
  "session_id": "8f14e45f-ea3c-4f3f-a1c2-4d7c8b6a9f01",
  "status": "waiting",
  "phone_number": "+201012345678",
  "gateway_phone_number": "+201001234567",
  "qr_code_data": "tel:+201001234567",
  "environment": "live",
  "expires_at": "2026-10-21T12:05:00.000Z"
}
  • استعلم كل 2–4 ثوانٍ أثناء queued أو waiting.
  • عندما يصبح waiting اعرض الرقم كرابط tel: واطلب رنة واحدة ثم إغلاق.
  • إذا انتهت الجلسة أنشئ جلسة جديدة؛ لا يمكن إحياء جلسة منتهية.
  • اسمح للمستخدم بإلغاء جلسة queued/waiting عند تغيير رأيه لتحرير الخط.

qr_code_data هو tel: URI لنفس رقم البوابة. ارسمه كـ QR بجانب الرقم حتى يمسحه مستخدم الديسكتوب بكاميرا الهاتف الذي سيجري المكالمة — دون كتابة. يكون null أثناء queued وبعد خروج الجلسة من waiting.

3. الاستعلام والإلغاء

الاستعلام والإلغاء
export async function getVerification(sessionId) {
  const res = await fetch(API + '/v1/verifications/' + encodeURIComponent(sessionId), {
    headers: { Authorization: 'Bearer ' + process.env.SHERNOVA_SECRET_KEY },
  });
  return res.json();
}

export async function cancelVerification(sessionId) {
  const res = await fetch(API + '/v1/verifications/' + encodeURIComponent(sessionId), {
    method: 'DELETE',
    headers: { Authorization: 'Bearer ' + process.env.SHERNOVA_SECRET_KEY },
  });
  return res.json();
}
الحالة والإلغاء
curl "https://api.shernova.com/v1/verifications/SESSION_UUID" \
  -H "Authorization: Bearer sk_live_YOUR_SECRET_KEY"

curl -X DELETE "https://api.shernova.com/v1/verifications/SESSION_UUID" \
  -H "Authorization: Bearer sk_live_YOUR_SECRET_KEY"

4. اعتبر verified نجاحًا

في التنفيذ الحالي، verified تعني أن رقم المتصل طابق الجلسة وأنه آمن للقبول. أما completed فتعني أن الجلسة سُلّمت أيضًا إلى Webhook. لا تنتظر completed عند الاستعلام: اقبل verified فورًا واجعل تحديث قاعدة بياناتك idempotent.

الحالةالمعنىما يجب أن يفعله موقعك
queuedلا يوجد خط متاح؛ رقم البوابة nullاستمر في الاستعلام واعرض رسالة انتظار
waitingتم تخصيص خط؛ اعرض رقم البوابةاطلب مكالمة فائتة واحدة
verifiedالمكالمة طابقت؛ تم إثبات الملكيةاقبل التحقق واحفظ النتيجة
completedتم تسليم Webhookتعامل معها كـ verified (idempotent)
expiredانتهت نافذة الاتصالاعرض محاولة جديدة
failed / rejectedتعذّر التحقق من المكالمةاعرض إعادة المحاولة
cancelledأُلغي بواسطتك أو بواسطة المستخدماعرض محاولة جديدة

5. Webhooks هي الخيار الأفضل في الإنتاج

تزيل Webhooks حاجة الاستعلام المستمر. تحقّق من توقيع X-Shernova-Signature (HMAC) على الجسم الخام قبل تحليل JSON، ثم عالج الحدث بشكل idempotent. راجع Webhooks لمعرفة الحقول وإعادة المحاولات.

التحقق من التوقيع في Express
import express from 'express';
import crypto from 'node:crypto';

const app = express();
app.post('/webhooks/shernova', express.raw({ type: 'application/json' }), (req, res) => {
  const received = String(req.headers['x-shernova-signature'] || '').replace(/^sha256=/, '');
  const expected = crypto
    .createHmac('sha256', process.env.SHERNOVA_WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex');

  const a = Buffer.from(expected);
  const b = Buffer.from(received);
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return res.status(401).send('invalid signature');
  }

  const event = JSON.parse(req.body.toString());
  if (event.status === 'verified' || event.status === 'completed') {
    // وثّق الهاتف/المستخدم في قاعدة بياناتك.
  }
  res.sendStatus(200);
});
التحقق من التوقيع في Flask
import hmac, hashlib, os
from flask import Flask, request

app = Flask(__name__)

@app.post("/webhooks/shernova")
def shernova_webhook():
    raw = request.get_data()
    received = request.headers.get("X-Shernova-Signature", "").removeprefix("sha256=")
    expected = hmac.new(
        os.environ["SHERNOVA_WEBHOOK_SECRET"].encode(), raw, hashlib.sha256
    ).hexdigest()
    if not hmac.compare_digest(expected, received):
        return "invalid signature", 401
    event = request.get_json(force=True)
    if event.get("status") in ("verified", "completed"):
        pass  # وثّق الرقم في قاعدة بياناتك
    return "", 200

6. الأخطاء التي يجب أن يعالجها خادمك

HTTPرمز الخطأالمعنى
400invalid_phoneرقم غير صالح بصيغة E.164
401unauthorizedمفتاح سري مفقود أو غير صالح
403phone_verification_requiredصاحب حساب Shernova يجب أن يتحقق من هاتفه أولًا
409phone_verification_in_progressتوجد جلسة معلّقة أخرى لنفس الرقم
429verification_rate_limitedحدود الرقم/IP/الجهاز؛ احترم retry_after_seconds
503maintenance_modeالخدمة متوقفة مؤقتًا؛ اعرض الرسالة ثنائية اللغة

إعادة المحاولة بنفس client_request_id تُرجع نفس الجلسة بدل إنشاء نسخة مكررة. استخدم UUID من سجل التسجيل/الطلب لديك.

7. قائمة الأمان والسعة

  • أبقِ sk_* على الخادم فقط؛ مرّر الإنشاء/الحالة/الإلغاء عبر خادمك.
  • حدّد معدل بدء الجلسات لكل مستخدم مصادق عليه، وليس لكل IP فقط.
  • اقبل فقط verified أو completed؛ لا تثق بأي علامة من المتصفح.
  • تحقّق من صيغة الرقم وطبّعها قبل الإرسال.
  • استخدم HTTPS وتحقق من توقيع Webhook على الجسم الخام.
  • لا تنشئ جلسة ثانية لرقم لديه جلسة نشطة؛ أعد استخدامها.
  • نظّف الجلسات المتروكة بالإلغاء أو بتركها تنتهي.

8. أمثلة جاهزة للتشغيل

يحتوي المستودع على أمثلة Node.js وPHP وPython بدون مكتبات إضافية في examples/web-integration/. كل خادم يوفّر إنشاء الجلسة والحالة والإلغاء واستقبال Webhook، ويقرأ المفتاح السري من متغير بيئة. ابدأ بملف README في المجلد.