API

مستندات اتصال

قیمت لحظه ای و سفارش‌گذاری به زیرساخت آرام گلد
راهنمای اتصال سرور به سرور

دسترسی قیمت لحظه ای

این راهنما برای تیم‌هایی است که فقط جریان لحظه ای قیمت را دریافت می‌کنند و به API سفارش دسترسی ندارند. تمام درخواست‌های HTTP به دامنه اپ Gold شما ارسال می‌شود و دریافت لحظه‌ای قیمت از کانال خصوصی Pusher/Soketi انجام می‌گیرد.

Server-to-Server Pusher Protocol v7 Private Channel Snapshot + Realtime X-SOCKET-KEY

تصویر کلی اتصال

در این نوع دسترسی، کانال به گروه قیمتی انتخاب‌شده هنگام صدور دسترسی متصل است و ثبت سفارش وجود ندارد.

هویت کانال

private-pricing-ext.{APP_ID}.pricing_group_{PRICE_GROUP_ID}.{CHANNEL_TOKEN}

مدل دریافت

یک Snapshot کامل برای شروع + رویدادهای لحظه‌ای external.price.changed.

مهم این دسترسی به یک گروه قیمت مشخص قفل است. تغییر گروه قیمت یک «مشتری» در Gold، این کانال را جابه‌جا نمی‌کند. رفتار کانال ثابت بر اساس مشتری فقط مربوط به دسترسی «قیمت + سفارش» است.

اطلاعات لازم از پنل

تمام مقادیر واقعی را از طریق مدیریت اپ مورد نظر دریافت و نگهداری آن دقت لازم را داشته باشید. هیچ مقدار محیطی را حدس نزنید چون IP شما در خطر بلاک قرار میگیرد.

متغیرکاربردنمونه حرفه‌ای
ACCESS_KEYدر هدر X-SOCKET-KEY{YOUR_ACCESS_KEY}
CHANNELنام کامل کانال خصوصیprivate-pricing-ext.{APP_ID}.pricing_group_{PRICE_GROUP_ID}.{CHANNEL_TOKEN}
AUTH_URLاحراز کانال خصوصیhttps://{GOLD_DOMAIN}/api/v1/platform/pricing-socket/auth
PRICES_URLSnapshot قیمت‌هاhttps://{GOLD_DOMAIN}/api/v1/platform/pricing-socket/prices
PUSHER_APP_KEYکلید اپ سوکت{PUSHER_APP_KEY}
PUSHER_HOSTمیزبان WebSocket{PUSHER_HOST}
PUSHER_PORTپورت TLS443
Secretها را در Frontend قرار ندهید ACCESS_KEY باید فقط در محیط سرور نگهداری شود. برای مرورگر، auth و snapshot را از Backend خودتان proxy کنید.

ترتیب استاندارد اتصال

در شروع برنامه و بعد از هر reconnect، این توالی را از ابتدا اجرا کنید.

۱
WebSocket را باز کنید

به URL پروتکل Pusher v7 متصل شوید.

۲
socket_id را دریافت کنید

از رویداد pusher:connection_established.

۳
از Gold احراز بگیرید

socket_id و CHANNEL را با X-SOCKET-KEY ارسال کنید.

۴
Subscribe کنید

رشته auth برگشتی را روی همان کانال استفاده کنید.

۵
منتظر subscription success بمانید

پیش از این مرحله استریم را آماده تلقی نکنید.

۶
Snapshot کامل را بگیرید

دفتر قیمت محلی را با پاسخ Snapshot بسازید یا جایگزین کنید.

۷
Tickها را اعمال کنید

فقط external.price.changed را روی لیست قیمت های خود اعمال کنید.

Snapshot قیمت

GET https://{GOLD_DOMAIN}/api/v1/platform/pricing-socket/prices

Snapshot منبع شروع لیست آخرین قیمت است و نام محصول را نیز در اختیار شما قرار می‌دهد.

هدرها

HTTP headers
Accept: application/json
X-SOCKET-KEY: {YOUR_ACCESS_KEY}

نمونه پاسخ

JSON
{
  "products": [
    {
      "product_id": 12,
      "product_name": "طلا ۱۸ عیار",
      "buy": {
        "p": 789600000,
        "v": 28835,
        "e": 1785185236,
        "r": 90,
        "t": "u"
      },
      "sell": {
        "p": 791000000,
        "v": 28836,
        "e": 1785185236,
        "r": 90,
        "t": "u"
      }
    }
  ]
}
Snapshot را چه زمانی بگیرید؟ بعد از هر subscribe موفق، بعد از reconnect، و هر زمان که می‌خواهید لیست قیمت خود را دوباره همگام کنید.

نمونه PHP / Laravel

PHP · Laravel HTTP Client
use Illuminate\Support\Facades\Http;

$snapshot = Http::withHeaders([
    'Accept' => 'application/json',
    'X-SOCKET-KEY' => env('GOLD_PRICE_ACCESS_KEY'),
])->get(config('services.gold.prices_url'))
  ->throw()
  ->json();

foreach ($snapshot['products'] as $row) {
    $productId = $row['product_id'];
    $productName = $row['product_name'] ?? '';

    $buy = $row['buy'] ?? [];
    $sell = $row['sell'] ?? [];
}

WebSocket و احراز کانال

جریان لحظه‌ای با پروتکل Pusher نسخه ۷ روی TLS برقرار می‌شود.

WSS wss://{PUSHER_HOST}:{PUSHER_PORT}/app/{PUSHER_APP_KEY}?protocol=7&client=php&version=1.0&flash=false

احراز کانال خصوصی

POST https://{GOLD_DOMAIN}/api/v1/platform/pricing-socket/auth
هدر

X-SOCKET-KEY: {YOUR_ACCESS_KEY}

Body

socket_id + channel_name

PHP · Laravel Auth Request
use Illuminate\Support\Facades\Http;

$auth = Http::withHeaders([
    'Accept' => 'application/json',
    'X-SOCKET-KEY' => env('GOLD_PRICE_ACCESS_KEY'),
])->asForm()->post(config('services.gold.auth_url'), [
    'socket_id' => $socketId,
    'channel_name' => config('services.gold.channel'),
])->throw()->json('auth');
Reconnect پس از قطع ارتباط، دوباره WebSocket را باز کنید، socket_id جدید بگیرید، دوباره auth کنید و سپس Snapshot تازه بگیرید. socket_id اتصال قبلی را reuse نکنید.

فرمت رویداد قیمت

نام event در Pusher خام دقیقاً external.price.changed است.

Realtime payload
{
  "product_id": 12,
  "price_type": "buy",
  "p": 789600000,
  "v": 28835,
  "e": 1785185236,
  "r": 90,
  "t": "u"
}
فیلدمعنینکته
product_idشناسه عددی محصولبرای match با Snapshot
price_typebuy یا sellسمت قیمت
pقیمت خامp > 0 قیمت لحظه ای - 0 منقضی شدن قیمت - -1 بسته بودن قیمت
vنسخه ورژن قیمتTickهای قدیمی/تکراری را دور بریزید
eزمان انقضای Unixاگر وجود داشته باشد
rثانیه باقی‌ماندهدر لحظه انتشار
tنوع رویداد فشردهu,tr,e,ec,m,cc
cکد ارزدر صورت غیر IRR
lpآخرین قیمت زندهبرای رویدادهای close/expire ممکن است وجود داشته باشد
leانقضای آخرین قیمت زندههمراه lp

کدهای t

کدمعنی
uقیمت جدید
trتمدید TTL بدون تغییر قیمت
eانقضای قیمت
ecانقضای ناشی از منبع بالادست
mبستن دستی
ccبستن ناشی از منبع بالادست
قانون نسخه مقدار v را برای هر product_id + price_type نگه دارید. اگر نسخه Tick جدید کوچک‌تر یا مساوی نسخه اعمال‌شده بود، آن Tick را نادیده بگیرید.

نمونه Worker در PHP / Laravel

این نمونه صرفاً الگوی پیاده‌سازی است. Secretها از env/config خوانده می‌شوند و هیچ مقدار محیطی داخل کد hard-code نشده است.

PHP · Long-running worker
use Illuminate\Support\Facades\Http;
use WebSocket\Client;

$host = config('services.gold.pusher_host');
$port = config('services.gold.pusher_port', 443);
$appKey = config('services.gold.pusher_app_key');
$channel = config('services.gold.channel');
$accessKey = env('GOLD_PRICE_ACCESS_KEY');

$wsUrl = sprintf(
    'wss://%s:%s/app/%s?protocol=7&client=php&version=1.0&flash=false',
    $host,
    $port,
    $appKey
);

$backoff = 1;

while (true) {
    try {
        $client = new Client($wsUrl, ['timeout' => 30, 'filter' => ['text']]);

        $established = json_decode($client->receive(), true);
        $socketId = json_decode($established['data'] ?? '{}', true)['socket_id'] ?? null;

        if (! is_string($socketId) || $socketId === '') {
            throw new RuntimeException('socket_id missing');
        }

        $auth = Http::withHeaders([
            'Accept' => 'application/json',
            'X-SOCKET-KEY' => $accessKey,
        ])->asForm()->post(config('services.gold.auth_url'), [
            'socket_id' => $socketId,
            'channel_name' => $channel,
        ])->throw()->json('auth');

        $client->text(json_encode([
            'event' => 'pusher:subscribe',
            'data' => ['channel' => $channel, 'auth' => $auth],
        ]));

        Http::withHeaders([
            'Accept' => 'application/json',
            'X-SOCKET-KEY' => $accessKey,
        ])->get(config('services.gold.prices_url'))->throw();

        $backoff = 1;

        while (true) {
            $message = json_decode($client->receive(), true);
            $event = $message['event'] ?? '';

            if ($event === 'pusher:ping') {
                $client->text(json_encode(['event' => 'pusher:pong', 'data' => []]));
                continue;
            }

            if ($event !== 'external.price.changed') {
                continue;
            }

            $payload = is_string($message['data'] ?? null)
                ? json_decode($message['data'], true)
                : ($message['data'] ?? []);

            // اینجا payload را روی دفتر قیمت product_id / price_type اعمال کنید.
        }
    } catch (Throwable $e) {
        report($e);
        sleep($backoff);
        $backoff = min(30, $backoff * 2);
    }
}

خطاها و امنیت

HTTPمعنیاقدام پیشنهادی
200Auth یا Snapshot موفقادامه جریان
422درخواست ناقص یا هدر/پارامتر لازم وجود نداردساخت request را اصلاح کنید
403کلید، کانال، IP یا وضعیت دسترسی مجاز نیستتنظیمات دسترسی را بررسی کنید
429Rate limitBackoff کنید؛ Retry-After را رعایت کنید
مرز امنیتی ( توصیه ای برای هماهنگی بیشتر ) آدرس یا Secret داخلی سرویس Pricing را نیاز ندارید و نباید ذخیره کنید. اتصال خارجی فقط از مسیر Gold انجام می‌شود.

چک‌لیست نهایی

✓
مقادیر واقعی را از مدیر سیستم دریافت کرده اید

کلید، کانال، Auth URL، Snapshot URL، host/port/app key.

✓
Secret فقط روی سرور است

هیچ X-SOCKET-KEYای در Frontend نرفته است.

✓
Connect → Auth → Subscribe → Snapshot → Events

همین ترتیب در reconnect هم اجرا می‌شود.

✓
Tickهای تکراری با v حذف می‌شوند

و p <= 0 به‌عنوان قیمت قابل نمایش/معامله در نظر گرفته نمی‌شود.

یک کلید مشترک برای Price Socket و Order API

دسترسی قیمت لحظه ای + سفارش‌گذاری

این راهنما برای مشتریانی است که علاوه بر قیمت لحظه ای، امکان ثبت و استعلام سفارش دارند. همان X-SOCKET-KEY هویت مشتری را برای سوکت قیمت و API سفارش مشخص می‌کند و customer_id هرگز از سمت کلاینت ارسال نمی‌شود.

Stable Customer Channel POST /orders GET /orders/{id} 409 PRICE_MISMATCH Callback Idempotency

تصویر کلی این دسترسی

قیمت و سفارش با یک Access مشترک کار می‌کنند، اما کانال قیمت بر اساس مشتری ثابت است.

۱
Access را از طریق مدیر سیستم دریافت کنید

کلید، کانال ثابت مشتری، اطلاعات سوکت و Callback URL.

۲
قیمت زنده را دریافت کنید

Snapshot + external.price.changed روی کانال customer_{CUSTOMER_ID}.

۳
همان قیمت خام را در سفارش بفرستید

قیمت توسط Gold بازنویسی نمی‌شود؛ stale price با 409 رد می‌شود.

۴
order_id را نگه دارید

برای GET و تطبیق Callback.

۵
وضعیت نهایی را از Callback دریافت کنید

برای ابهام یا همگام‌سازی، GET استفاده کنید. سوکت وضعیت سفارش خارجی وجود ندارد.

کانال ثابت است اگر گروه قیمت مشتری تغییر کند، ACCESS_KEY، CHANNEL_TOKEN و نام کانال تغییر نمی‌کنند. قیمت‌های گروه جدید روی همان کانال منتشر می‌شوند و resubscribe لازم نیست.

Authentication و Secretها

هویت مشتری فقط از کلید Access تشخیص داده می‌شود.

Required headers
X-SOCKET-KEY: {YOUR_ACCESS_KEY}
Accept: application/json

فیلدهایی که نباید در body ارسال شوند

هویت

customer_id و created_by

کلید را محرمانه نگه دارید X-SOCKET-KEY فقط در env/secret manager سرور شما باشد. آن را داخل JavaScript مرورگر، لاگ عمومی یا URL قرار ندهید.

اطلاعات لازم از پنل

متغیرکاربردمقدار نمونه امن
ACCESS_KEYسوکت قیمت + POST/GET سفارش{YOUR_ACCESS_KEY}
CHANNELکانال ثابت مشتریprivate-pricing-ext.{APP_ID}.customer_{CUSTOMER_ID}.{CHANNEL_TOKEN}
AUTH_URLاحراز کانال قیمتhttps://{GOLD_DOMAIN}/api/v1/platform/pricing-socket/auth
PRICES_URLSnapshot قیمتhttps://{GOLD_DOMAIN}/api/v1/platform/pricing-socket/prices
ORDERS_URLثبت سفارشhttps://{GOLD_DOMAIN}/api/v1/platform/orders
CALLBACK_URLدریافت وضعیت نهاییhttps://api.example.com/gold/order-status

قیمت زنده روی کانال مشتری

پروتکل اتصال همان Snapshot + Pusher/Soketi است، با یک تفاوت کلیدی: کانال به مشتری متصل است، نه مستقیماً به نام گروه قیمت.

نام کانال

private-pricing-ext.{APP_ID}.customer_{CUSTOMER_ID}.{CHANNEL_TOKEN}

منبع قیمت

گروه قیمت فعلی مشتری در Backend؛ در نام کانال نمایش داده نمی‌شود.

قانون قیمت برای سفارش اگر p > 0 است، همان integer خام را در فیلد price سفارش بفرستید. p = 0 یا p = -1 یعنی آن سمت بسته است و نباید سفارش ارسال شود.
PHP · Auth + Snapshot
use Illuminate\Support\Facades\Http;

$headers = [
    'Accept' => 'application/json',
    'X-SOCKET-KEY' => env('GOLD_ORDER_ACCESS_KEY'),
];

$auth = Http::withHeaders($headers)
    ->asForm()
    ->post(config('services.gold.auth_url'), [
        'socket_id' => $socketId,
        'channel_name' => config('services.gold.order_channel'),
    ])
    ->throw()
    ->json('auth');

$snapshot = Http::withHeaders($headers)
    ->get(config('services.gold.prices_url'))
    ->throw()
    ->json();

ثبت سفارش

POST https://{GOLD_DOMAIN}/api/v1/platform/orders

هدرها

هدرالزامتوضیح
X-SOCKET-KEYالزامیهویت مشتری
Content-Typeالزامیapplication/json
Acceptپیشنهادیapplication/json
Idempotency-KeyاختیاریUUID معتبر برای retry امن شبکه

Body مجاز

فیلدقانون
product_idInteger؛ محصول باید در گروه قیمت فعلی مشتری، visible و transactional برای سمت سفارش باشد.
typebuy یا sell
priceدقیقاً همان p خام سوکت
trade_measurement_typequantity_based، weight_based یا value_based مطابق نوع محصول
amountبرای quantity/weight؛ در quantity باید integer باشد
totalفقط برای value_based
descriptionاختیاری، حداکثر ۱۰۰۰ کاراکتر
معنی trade_measurement_type اگر کالا بر اساس تعداد معامله شود، مقدار این فیلد quantity_based است. اگر کالا بر اساس وزن معامله شود، weight_based. اگر بر اساس مجموع معامله شود — یعنی کاربر بخواهد مثلاً به اندازه یک میلیارد گرم ۱۸ بخرد — باید value_based باشد و به‌جای amount فیلد total فرستاده شود.
quantity_based
{
  "product_id": 3,
  "type": "buy",
  "price": 983000000,
  "trade_measurement_type": "quantity_based",
  "amount": 2
}
weight_based
{
  "product_id": 1,
  "type": "sell",
  "price": 882000000,
  "trade_measurement_type": "weight_based",
  "amount": 12.5
}
value_based
{
  "product_id": 3,
  "type": "buy",
  "price": 983000000,
  "trade_measurement_type": "value_based",
  "total": 1966000000
}

نمونه PHP / Laravel

PHP · Laravel HTTP Client
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;

$response = Http::withHeaders([
    'Accept' => 'application/json',
    'X-SOCKET-KEY' => env('GOLD_ORDER_ACCESS_KEY'),
    'Idempotency-Key' => (string) Str::uuid(),
])->post(config('services.gold.orders_url'), [
    'product_id' => 3,
    'type' => 'buy',
    'price' => $livePriceFromSocket,
    'trade_measurement_type' => 'quantity_based',
    'amount' => 2,
]);

if ($response->status() === 201) {
    $orderId = $response->json('order_id');
}

پاسخ موفق

201 Created
{
  "status": true,
  "order_id": "{ORDER_ID}"
}

PRICE_MISMATCH — قیمت stale

Gold قیمت ارسالی شما را silently جایگزین نمی‌کند. قیمت باید دقیقاً با قیمت زنده موتور سفارش یکسان باشد.

409 Conflict
{
  "error": "PRICE_MISMATCH",
  "expected_price": 1234,
  "submitted_price": 1000
}
رفتار صحیح کلاینت قیمت جدید را به کاربر/منطق شما برگردانید و یک سفارش جدید با قیمت جدید ثبت کنید. اگر سفارش جدید می‌سازید، Idempotency-Key جدید تولید کنید. اگر فقط به‌خاطر timeout همان درخواست قبلی را retry می‌کنید، UUID قبلی را تکرار کنید.

Idempotency برای Retry امن

برای جلوگیری از ثبت دوباره سفارش در timeoutهای شبکه از یک UUID ثابت برای همان تلاش منطقی استفاده کنید.

Timeout

همان UUID قبلی را دوباره ارسال کنید.

سفارش جدید

UUID جدید تولید کنید.

UUID نامعتبر

درخواست با 422 رد می‌شود.

پنجره فعلی سرویس سفارش فعلاً dedupe را در پنجره ۳۰۰ ثانیه انجام می‌دهد. اگر این عدد در قرارداد عمومی شما تضمین نشده، Client نباید منطق کسب‌وکارش را به همین عدد hard-code کند.

استعلام وضعیت سفارش

GET https://{GOLD_DOMAIN}/api/v1/platform/orders/{ORDER_ID}

هر کلید فقط سفارش‌های متعلق به همان مشتری را می‌بیند. شناسه سفارش مشتری دیگر نیز به‌صورت 404 پاسخ داده می‌شود.

200 OK
{
  "order_id": "{ORDER_ID}",
  "product_id": 3,
  "type": "buy",
  "price": "983000000",
  "amount": "2",
  "total": "1966000000",
  "status": "PROCESSING",
  "created_at": "2026-09-02T20:15:01+00:00"
}
PHP · Laravel GET
use Illuminate\Support\Facades\Http;

$order = Http::withHeaders([
    'Accept' => 'application/json',
    'X-SOCKET-KEY' => env('GOLD_ORDER_ACCESS_KEY'),
])->get(
    rtrim(config('services.gold.orders_url'), '/').'/'.$orderId
)->throw()->json();

Callback وضعیت نهایی

برای سفارش خارجی، سوکت وضعیت وجود ندارد. وضعیت نهایی از طریق HTTP Callback اعلام می‌شود.

APPROVED

سفارش تأیید شده است.

REJECTED

سفارش رد شده است.

EXPIRED

سفارش منقضی شده است.

Callback payload
{
  "order_id": "{ORDER_ID}",
  "status": "APPROVED"
}
Callback را idempotent پردازش کنید تحویل شبکه‌ای exactly-once تضمین نمی‌شود. به‌علت retry ممکن است همان {order_id, status} بیش از یک‌بار برسد. کلید dedupe پیشنهادی سمت شما: order_id + status.

نمونه Receiver در Laravel

PHP · Laravel Controller
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

public function __invoke(Request $request)
{
    $data = $request->validate([
        'order_id' => ['required', 'string'],
        'status' => ['required', 'in:APPROVED,REJECTED,EXPIRED'],
    ]);

    DB::transaction(function () use ($data) {
        $key = $data['order_id'].'|'.$data['status'];

        // روی این کلید یک unique constraint / dedupe واقعی داشته باشید.
        // اگر قبلاً پردازش شده، no-op کنید.

        // وضعیت سیستم خودتان را به‌روزرسانی کنید.
    });

    return response()->json(['ok' => true]);
}

کدهای HTTP

کدمعنیرفتار Client
201سفارش پذیرفته شدorder_id را ذخیره کنید
200GET/Auth/Snapshot موفقادامه
401کلید سفارش وجود ندارد یا ناشناخته استCredential را بررسی کنید
403Access غیرفعال/منقضی یا IP غیرمجازتنظیمات دسترسی را بررسی کنید
404سفارش برای این مشتری پیدا نشدشناسه/مالکیت را بررسی کنید
409PRICE_MISMATCHبا قیمت جدید سفارش تازه بفرستید
422Validation یا Business RuleBody یا وضعیت کسب‌وکار را اصلاح کنید
429Rate limitBackoff و Retry-After
503زیرساخت سفارش موقتاً آماده نیستسفارش را ثبت‌شده فرض نکنید؛ بعداً retry کنید

قوانین کسب‌وکار

گروه قیمت فعلی

محصول باید در گروه قیمت زنده مشتری وجود داشته باشد.

وضعیت حساب و کالا

حساب فعال و سمت buy/sell محصول transactional باشد.

سقف‌ها و حداقل/حداکثر

سقف روزانه/شب و محدوده تعداد یا وزن اعمال می‌شوند.

اعتبار و موجودی

خرید و فروش بر اساس اعتبار/موجودی واقعی مشتری کنترل می‌شوند.

سفارش همزمان

اگر تنظیم سفارش همزمان غیرفعال باشد، سفارش فعال قبلی می‌تواند سفارش جدید را مسدود کند.

قیمت زنده

قیمت ارسالی باید با قیمت فعلی موتور برابر باشد؛ در غیر این صورت 409.

جریان پیشنهادی یک معامله

۱
کانال مشتری را Subscribe کنید

بعد از موفقیت، Snapshot بگیرید.

۲
قیمت زنده را انتخاب کنید

فقط p > 0 را قابل معامله بدانید.

۳
UUID بسازید

برای Idempotency-Key.

۴
POST سفارش

همان قیمت خام سوکت را ارسال کنید.

۵
409؟

قیمت جدید را بگیرید و با UUID جدید سفارش تازه ثبت کنید.

۶
Timeout؟

همان UUID را retry کنید.

۷
Callback را idempotent اعمال کنید

در ابهام، GET وضعیت را بخوانید.

امنیت

Secretها

Access Key فقط در env/secret manager. نه Frontend، نه URL و نه log عمومی.

Callback

از HTTPS استفاده کنید و endpoint را عمومی اما کنترل‌شده نگه دارید. وضعیت‌ها را idempotent پردازش کنید.

ویرایش سفارش

سفارش‌های API از پنل Gold قابل ویرایش دستی نیستند.

چک‌لیست نهایی

✓
یک کلید مشترک دارید

برای Price Socket و Order API.

✓
customer_id ارسال نمی‌کنید

هویت فقط از روی Access Key تعیین می‌شود.

✓
کانال بر اساس مشتری ثابت است

تغییر گروه قیمت resubscribe نمی‌خواهد.

✓
409 و Idempotency را درست هندل کرده‌اید

Price mismatch سفارش تازه؛ timeout همان UUID.

✓
Callback idempotent است

دریافت تکراری همان order_id + status مشکلی ایجاد نمی‌کند.