افزونه را نصب می‌کنید، تنظیمات را پر می‌کنید و روی دکمه «تست اتصال» می‌زنید؛ نتیجه: یک پیام مبهم مثل Connection failed، یک خطای cURL با کد 28، یا هیچ پاسخی در ازای انتظاری چند ثانیه‌ای. اگر با خطای عدم اتصال افزونه به API روبرو هستید، این مقاله همان مسیری را طی می‌کند که در سال‌ها کار روی صدها پروژه واقعی وردپرس بارها و بارها پیموده‌ام. برخلاف خطاهای CSS یا JS که در مرورگر اتفاق می‌افتند، خطای اتصال به API بین دو سرور رخ می‌دهد و همین آن را به یکی از مبهم‌ترین انواع خطا در توسعه وردپرس تبدیل می‌کند. در عمل همیشه در یکی از پنج لایه مشخص ریشه دارد: احراز هویت نادرست، URL و endpoint اشتباه، پیکربندی نادرست درخواست، فایروال سرور یا WAF، و مدیریت نادرست پاسخ یا timeout. اگر این پنج لایه را به ترتیب بررسی کنید، تقریباً همیشه به علت دقیق می‌رسید بدون آنکه ساعت‌ها وقت خود را صرف آزمون‌وخطا کنید.

خطای عدم اتصال به API — تفکیک نشانه‌ها

قبل از هر اقدامی باید مشخص کنید کدام یک از این شش نشانه را می‌بینید، چون هرکدام جهت عیب‌یابی را به لایه متفاوتی هدایت می‌کند. نشانه اول: خطای cURL error 28: Operation timed out. این نشانه به لایه چهارم (فایروال) یا لایه پنجم (timeout) اشاره دارد. نشانه دوم: کد وضعیت HTTP 401 (Unauthorized) یا 403 (Forbidden). این دو کد، مستقیماً به لایه اول (احراز هویت) مربوط می‌شوند و معمولاً نشان می‌دهند کلید API اشتباه است یا منقضی شده. اگر با معماری کلی افزونه‌های وردپرس آشنایی ندارید، بهتر است ابتدا نگاهی به افزونه وردپرس چیست بیندازید.

نشانه سوم: خطای SSL certificate problem: unable to get local issuer certificate یا مشابه. این نشانه مستقیماً به لایه دوم (SSL) اشاره دارد و در پروژه‌هایی که روی هاست اشتراکی یا سرورهای قدیمی اجرا می‌شوند شایع است. نشانه چهارم: کد 404 یا 405. اولی نشان می‌دهد endpoint اشتباه است و دومی نشان می‌دهد روش HTTP (مثل GET به‌جای POST) نادرست است. نشانه پنجم: پاسخ خالی یا پیام موفقیت ولی داده‌های ناقص. این حالت به لایه سوم (پیکربندی درخواست) یا لایه پنجم (مدیریت پاسخ) برمی‌گردد. نشانه ششم: روی محیط محلی کار می‌کند ولی روی سرور نه، یا برعکس — این حالت تقریباً همیشه به تفاوت‌های محیطی مثل فایروال، نسخه OpenSSL، یا پیکربندی cURL مربوط می‌شود.

در عیب‌یابی خطای اتصال به API، اولین سوال این نیست که «کد من کجاست» بلکه این است «آیا درخواست اصلاً از سرور من خارج می‌شود؟». تفاوت میان «خارج شد و پاسخ نادرست گرفت» و «خارج نشد» کل مسیر تشخیص را تغییر می‌دهد.

ارتباط HTTP در وردپرس چطور کار می‌کند؟

وردپرس برای ارتباط با APIهای خارجی یک لایه انتزاعی به نام WP_HTTP دارد که به‌طور پیش‌فرض روی cURL یا Streams پیاده‌سازی می‌شود. توابع wp_remote_get، wp_remote_post، و wp_remote_request به‌جای استفاده مستقیم از cURL، همه این لایه انتزاعی را به کار می‌گیرند. مزیت این لایه، سازگاری بیشتر با محیط‌های مختلف و امکان جایگزینی توسط افزونه‌هاست؛ اما خود این لایه منشأ برخی از خطاهای ظاهراً مرموز است. اگر با مفهوم افزونه وردپرس و لایه‌های معماری آن آشنا نیستید، توصیه می‌کنم آن مقاله را مرور کنید.

هر درخواست HTTP از سمت وردپرس، پنج فاز را طی می‌کند که درک هر فاز برای عیب‌یابی ضروری است. فاز اول: ساخت درخواست شامل URL، هدرها، بدنه و متد. فاز دوم: انتخاب transport layer (cURL یا Streams). فاز سوم: ارسال درخواست به سرور مقصد. فاز چهارم: دریافت پاسخ شامل کد وضعیت، هدرها و بدنه. فاز پنجم: پارس پاسخ و بازگشت به کد فراخوانی‌کننده. هرکدام از این پنج فاز می‌تواند شکست بخورد و شکست در هر فاز، پیام خطای متفاوتی تولید می‌کند. برای مرور اصول ساخت API در وردپرس و درک چرخه کامل، ساخت API اختصاصی برای وردپرس و برای ارتباط با سرویس‌های خارجی، اتصال وردپرس به سرویس‌های خارجی با API را ببینید.

کد وضعیتمعنالایه مرتبط
401احراز هویت نامعتبر یا منقضیلایه اول
403دسترسی ممنوع — ممکن است فایروال یا scope نامعتبرلایه اول یا چهارم
404Endpoint اشتباه یا تغییر مسیر نادرستلایه دوم
405روش HTTP نامعتبر (POST به‌جای GET)لایه سوم
429Rate limit — تعداد درخواست‌های زیادلایه پنجم
500 / 502 / 503خطای سرور مقصد یا پاسخ ناقصلایه پنجم یا خارج از کنترل شما
cURL 28Timeout — اتصال برقرار نشدلایه چهارم یا پنجم
cURL 60SSL certificate problemلایه دوم

این جدول، سرنخ بسیاری از عیب‌یابی‌ها را به شما می‌دهد. اگر می‌خواهید بدانید چطور می‌توانید این کدها را در بافت وردپرس بگیرید و تفسیر کنید، مرور آموزش REST API و تست API دید خوبی می‌دهد.

لایه اول — احراز هویت: کلید API، OAuth و JWT

شایع‌ترین علت خطای اتصال به API، احراز هویت نادرست است. اگر API شما کد 401 یا 403 برگرداند، تقریباً همیشه این لایه مقصر است. سه مکانیزم رایج احراز هویت که در هرکدام ممکن است اشتباه رخ دهد:

کلید API در هدر یا query string

بسیاری از APIهای مدرن از کلید API در هدر استفاده می‌کنند. الگوی درست در وردپرس:

$response = wp_remote_post(
    'https://api.example.com/v1/endpoint',
    array(
        'headers' => array(
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type'  => 'application/json',
            'Accept'        => 'application/json',
        ),
        'body' => wp_json_encode( $payload ),
        'timeout' => 30,
    )
);

اشتباهات رایج در این لایه: اول، کلید API با فاصله اضافه یا کاراکتر پنهان ذخیره شده است — این مورد شایع‌تر از آن است که فکر می‌کنید و در تنظیمات کاربران کپی/پیست می‌شود. همیشه با trim() کلید را پاک‌سازی کنید. دوم، نبود کلمه Bearer در ابتدای هدر — بعضی APIها این را لازم دارند و بعضی نه؛ مستندات هر API صادق‌ترین منبع است. سوم، استفاده از کلید تستی در محیط تولید یا برعکس. برای مطالعه بیشتر درباره اصول احراز هویت در REST API، احراز هویت در REST API را ببینید.

OAuth و توکن‌های دسترسی موقت

در APIهایی مثل Google، Twitter یا GitHub، احراز هویت از طریق OAuth انجام می‌شود. در این حالت دو نوع توکن وجود دارد: access token که منقضی می‌شود (معمولاً در بازه یک ساعت تا یک ماه) و refresh token که برای تجدید access token استفاده می‌شود. خطای 401 پس از چند ساعت کارکرد درست، تقریباً همیشه نشانه منقضی شدن access token است. راه‌حل: پیاده‌سازی مکانیزم refresh خودکار:

if ( ! get_transient( 'my_plugin_access_token' ) ) {
    $refresh_response = wp_remote_post(
        'https://oauth.example.com/token',
        array(
            'body' => array(
                'grant_type'    => 'refresh_token',
                'refresh_token' => get_option( 'my_plugin_refresh_token' ),
                'client_id'     => get_option( 'my_plugin_client_id' ),
                'client_secret' => get_option( 'my_plugin_client_secret' ),
            ),
        )
    );
    $body = wp_remote_retrieve_body( $refresh_response );
    $data = json_decode( $body, true );
    if ( ! empty( $data['access_token'] ) ) {
        set_transient( 'my_plugin_access_token', $data['access_token'], $data['expires_in'] - 60 );
    }
}

نکته مهم: همیشه توکن را با حاشیه امن ذخیره کنید (مثلاً ۶۰ ثانیه کمتر از زمان رسمی انقضا) تا از مشکل «توکن در لحظه درخواست منقضی شد» جلوگیری شود. برای مطالعه مکانیزم‌های احراز هویت مدرن، OAuth چیست و چگونه کار می‌کند و JWT چیست و چه کاربردی در احراز هویت دارد را ببینید.

JWT و امضای دیجیتال

در برخی APIها، توکن احراز هویت از نوع JWT است و باید با یک کلید خصوصی امضا شود. اگر امضا نادرست باشد، سرور با کد 401 پاسخ می‌دهد بدون توضیح بیشتر. سه اشتباه رایج در این لایه: اول، استفاده از کلید عمومی به‌جای کلید خصوصی. دوم، نادرست بودن الگوریتم امضا (مثلاً HS256 به‌جای RS256). سوم، فراموش کردن فیلد aud یا iss در payload. برای مرور اصول کامل این مکانیزم، همان مرجع JWT چیست توصیه می‌شود.

ذخیره امن کلیدها

نکته حیاتی در بافت احراز هویت: هرگز کلید API را در کد افزونه به‌صورت هاردکد ذخیره نکنید. برای ذخیره امن کلیدها در وردپرس، از دو روش استفاده کنید: اول، ذخیره در wp_options با نام‌گذاری غیرقابل حدس. دوم، ترجیحاً ذخیره در wp-config.php به‌عنوان ثابت و خواندن با defined(). برای مرور مسائل امنیتی مرتبط با API و کلیدها، امنیت API را ببینید.

در خطای اتصال به API، اولین چیزی که باید بررسی کنید کلید و توکن است. من در پروژه‌های واقعی دیده‌ام که تیم‌ها روزها روی تنظیمات درخواست کار کرده‌اند در حالی که کلید API با یک فاصله اضافه ذخیره شده بود. یک trim() ساده، مشکل را حل می‌کرد.

لایه دوم — URL، endpoint و مشکلات SSL

لایه دوم جایی است که احراز هویت درست است ولی درخواست به مقصد اشتباه می‌رود. این حالت معمولاً خودش را با کد 404، 301/302 نامناسب یا خطای SSL نشان می‌دهد. سه زیرگروه مهم در این لایه وجود دارد:

URL و endpoint اشتباه

شایع‌ترین اشتباه در این زیرگروه، نبود یا اضافه بودن اسلش پایانی URL است. بعضی APIها /v1/users را قبول می‌کنند و /v1/users/ را نه. بعضی برعکس. اشتباه دوم: نبود نسخه API در URL (مثل /v1 یا /v2) که بسیاری از APIها آن را لازم دارند. اشتباه سوم: استفاده از endpoint تستی در محیط تولید. راه‌حل عملی: قبل از کدنویسی، همیشه با ابزاری مثل Postman یا cURL، درخواست را به‌طور دستی تست کنید و URL صحیح را ثابت کنید. اگر با Postman آشنا نیستید، تست REST API با Postman را ببینید.

HTTPS و مشکلات SSL

اگر سایت شما روی HTTPS است و API مقصد هم HTTPS اجباری دارد، معمولاً مشکلی وجود ندارد. اما در برخی سرورهای قدیمی یا هاست‌های اشتراکی با OpenSSL قدیمی، خطای cURL error 60: SSL certificate problem رخ می‌دهد. سه راه‌حل برای این مسئله:

  1. به‌روزرسانی CA bundle در سرور: اکثر سرورهای مدرن این را به‌طور خودکار مدیریت می‌کنند، ولی هاست‌های قدیمی ممکن است فایل CA را نداشته باشند. در این حالت، باید از مدیر سرور یا پشتیبانی هاست درخواست به‌روزرسانی کنید.
  2. مشخص کردن مسیر CA bundle در کد: اگر دسترسی به فایل CA دارید، می‌توانید مسیر را در درخواست مشخص کنید:
    $response = wp_remote_get(
        $url,
        array(
            'sslverify' => true,
            'sslcertificates' => '/path/to/cacert.pem',
            'timeout' => 30,
        )
    );
  3. غیرفعال کردن موقت sslverify: فقط برای دیباگ در محیط محلی یا استجینگ. این روش را هرگز روی محیط تولید استفاده نکنید چون سایت شما را در برابر حمله MITM آسیب‌پذیر می‌کند.

برای مرور دقیق مکانیزم SSL و نقش آن در ارتباطات، SSL چیست و چرا سایت به آن نیاز دارد را ببینید. همچنین اگر با مفاهیم مرتبط با HTTPS و SSL دسترسی دارید، مرور مکانیزم عملی در HTTPS چیست و چه تفاوتی با HTTP دارد توصیه می‌شود.

Mixed Content در URLهای داخلی سایت

اگر افزونه شما به API داخلی وردپرس (مثل wp-json) متصل می‌شود، مهم است که URL از همان پروتکل و دامنه استفاده کند. اگر سایت روی HTTPS است ولی URL داخلی با http:// ساخته شده، مرورگر درخواست AJAX را بلاک می‌کند و در کنسول پیام Mixed Content می‌بینید. راه‌حل استاندارد:

$api_url = rest_url( 'my-plugin/v1/endpoint' );
// نه:
$api_url = 'http://' . $_SERVER['HTTP_HOST'] . '/wp-json/my-plugin/v1/endpoint';

تابع rest_url() به‌طور خودکار پروتکل و دامنه صحیح را می‌سازد و برای همین توصیه می‌شود. اگر URL داخلی را دستی می‌سازید، همیشه از توابع استاندارد وردپرس مثل home_url()، site_url() یا admin_url() استفاده کنید.

لایه سوم — پیکربندی درخواست، هدرها و body

لایه سوم جایی است که احراز هویت درست است، URL صحیح است، ولی درخواست از نظر ساختاری اشتباه است. کدهای وضعیت شایع در این لایه: 405 (Method Not Allowed)، 415 (Unsupported Media Type)، 400 (Bad Request). این کدها همگی نشان می‌دهند که سرور درخواست شما را فهمیده ولی نمی‌تواند آن را پردازش کند.

روش HTTP (GET، POST، PUT، DELETE، PATCH)

هر endpoint یک روش HTTP مشخص انتظار دارد. اگر endpoint انتظار POST دارد و شما GET بفرستید، کد 405 برمی‌گردد. الگوی درست در وردپرس:

$response = wp_remote_request(
    'https://api.example.com/v1/users/42',
    array(
        'method'  => 'PUT',
        'headers' => array(
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type'  => 'application/json',
        ),
        'body'    => wp_json_encode( array( 'name' => 'Ali' ) ),
        'timeout' => 30,
    )
);

نکته مهم: تابع wp_remote_request برای همه روش‌های HTTP کاربرد دارد، در حالی که wp_remote_get و wp_remote_post فقط برای همان دو روش استفاده می‌شوند. اگر در درخواست از متد PUT استفاده می‌کنید ولی از wp_remote_post بهره می‌گیرید، عملاً POST می‌فرستید که منجر به کد 405 می‌شود.

هدرهای Content-Type و Accept

یکی از شایع‌ترین اشتباهات در این زیرگروه، فراموش کردن هدر Content-Type: application/json است. اگر API مقصد انتظار JSON دارد و شما بدنه را JSON می‌فرستید ولی Content-Type را مشخص نمی‌کنید، وردپرس به‌طور پیش‌فرض application/x-www-form-urlencoded می‌فرستد و سرور کد 415 یا 400 برمی‌گرداند. الگوی درست:

'headers' => array(
    'Authorization' => 'Bearer ' . $api_key,
    'Content-Type'  => 'application/json',
    'Accept'        => 'application/json',
),

هدر Accept هم به‌خاطر دارد. بعضی APIها اگر Accept نداشته باشد، پاسخ را به‌صورت XML برمی‌گردانند و کد شما که انتظار JSON دارد، خطای parse می‌گیرد. اگر با معماری REST و ساختار پاسخ در وردپرس آشنا نیستید، REST API چیست را ببینید.

بدنه درخواست و کدگذاری

اگر بدنه درخواست را به‌عنوان آرایه می‌فرستید و انتظار JSON دارید، باید بدنه را با wp_json_encode تبدیل کنید. اگر مستقیم آرایه بفرستید، وردپرس آن را به‌صورت فرم‌داده ارسال می‌کند:

// نادرست اگر API انتظار JSON دارد:
'body' => array( 'key' => 'value' ),

// درست:
'body' => wp_json_encode( array( 'key' => 'value' ) ),

اشتباه دیگر: ارسال داده‌های پیچیده بدون encoding صحیح. اگر بدنه شما شامل کاراکترهای یونیکد فارسی یا ایموجی است، باید از wp_json_encode استفاده کنید تا escape صحیح انجام شود. تابع PHP اصلی json_encode هم این کار را انجام می‌دهد ولی wp_json_encode برای اطمینان از سازگاری با نسخه‌های مختلف PHP و وردپرس بهتر است.

کدگذاری احراز هویت Basic

اگر API از Basic Auth استفاده می‌کند، باید نام کاربری و رمز عبور را با جداکننده : ترکیب و با base64 کدگذاری کنید:

'headers' => array(
    'Authorization' => 'Basic ' . base64_encode( $username . ':' . $password ),
),

نکته امنیتی: از Basic Auth فقط روی HTTPS استفاده کنید چون این هدر بدون رمزنگاری ارسال می‌شود و در صورت رهگیری، اطلاعات شما در معرض خطر است. برای مرور اصول امنیتی ارتباط با API، امنیت API را ببینید.

در لایه سوم، جزئیات کوچک بیشترین اثر را دارند. یک هدر Content-Type فراموش‌شده، یک اسلش اضافه در URL، یا یک فیلد نادرست در body، می‌تواند تفاوت بین درخواست موفق و شکست کامل باشد. اینجا دقت، همه چیز است.

لایه چهارم — فایروال، WAF و محدودیت سرور

لایه چهارم جایی است که کد شما بی‌نقص است، درخواست صحیح ساخته می‌شود، ولی از سمت سرور شما یا سرور مقصد مسدود می‌شود. این لایه، دشوارترین لایه برای تشخیص است چون خطاها مبهم و گاهی متناوب هستند.

فایروال هاست و بلاک IP

اکثر هاست‌های اشتراکی یک لایه فایروال نرم‌افزاری یا سخت‌افزاری دارند که ترافیک خروجی یا ورودی را فیلتر می‌کند. اگر IP سرور شما در لیست سیاه سرور مقصد باشد (مثلاً به‌دلیل استفاده اشتراکی از IP توسط یک سایت آلوده)، درخواست شما بلاک می‌شود. راه تشخیص: با ابزاری مثل cURL از خط فرمان سرور خودتان، یک درخواست ساده به یک سرویس خارجی مثل https://api.ipify.org بفرستید و ببینید آیا پاسخ می‌گیرید یا نه. اگر پاسخ نمی‌گیرید، احتمالاً ترافیک خروجی سرور بلاک است و باید با پشتیبانی هاست تماس بگیرید.

WAF و mod_security

بعضی هاست‌ها WAF (Web Application Firewall) دارند که ترافیک ورودی و خروجی را بر اساس الگوهای امنیتی فیلتر می‌کند. اگر درخواست شما شامل الگویی باشد که WAF آن را مشکوک تشخیص دهد (مثلاً پارامترهای طولانی یا الگوهای شبیه SQL injection در بدنه)، درخواست بلاک می‌شود و معمولاً کد 403 یا صفحه‌ای شبیه صفحه امنیتی برگردانده می‌شود. راه‌حل: مستندات WAF هاست را بررسی کنید و اگر قوانین قابل تنظیم دارند، درخواست‌های افزونه خود را استثنا کنید. اگر WAF هاست قابلیت تنظیم ندارد، ممکن است مجبور باشید درخواست را با هدرهای استانداردتر یا حجم کوچک‌تر بفرستید.

محدودیت‌های خروجی در هاست

برخی هاست‌های اشتراکی، ترافیک خروجی به پورت‌های خاص یا به IPهای خاص را بلاک می‌کنند. مثلاً بعضی هاست‌ها فقط پورت‌های 80 و 443 را اجازه می‌دهند و پورت‌های دیگر (مثل 8080 یا 9200) بلاک هستند. اگر API شما روی پورتی غیر از 443 اجرا می‌شود، باید با پشتیبانی هاست چک کنید. نکته دیگر: بعضی هاست‌ها محدودیت پهنای باند ماهانه دارند و اگر به سقف نزدیک شوید، درخواست‌های خروجی شما throttle می‌شود. برای مطالعه تفاوت کیفیت هاست‌ها در این زمینه، تأثیر هاست بر سرعت سایت را ببینید. اگر با محدودیت منابع مواجه هستید، اصول کاهش مصرف منابع هاست می‌تواند کمک کند.

DNS و مشکلات resolve

اگر سرور شما نتواند نام دامنه API را resolve کند، خطای Could not resolve host دریافت می‌کنید. این خطا یا از DNS سرور شما می‌آید (اگر DNS اشتباه تنظیم شده) یا از firewall شبکه (اگر کوئری DNS بلاک شده). راه تشخیص: در SSH سرور، دستور nslookup api.example.com یا dig api.example.com را اجرا کنید. اگر پاسخ نگرفتید، مشکل DNS است. راه‌حل: تنظیم DNS معتبر روی سرور (مثلاً 1.1.1.1 یا 8.8.8.8) و تست مجدد. اگر روی هاست اشتراکی هستید و به DNS دسترسی ندارید، باید پشتیبانی هاست اقدام کند.

فایروال داخلی سرور مقصد

گاهی مشکل از سمت شما نیست؛ API مقصد خودش IP شما را بلاک کرده است. این حالت در APIهایی که rate limiting سخت‌گیرانه دارند شایع است. نشانه: کد 403 ثابت (نه متناوب) که حتی با کلید API معتبر هم برمی‌گردد. راه‌حل: از صفحه وضعیت API مقصد، درخواست unblock کنید یا از پشتیبانی API بخواهید IP شما را از لیست سیاه خارج کند. اگر این امکان وجود ندارد، ممکن است لازم باشد از IP دیگری (مثلاً از طریق یک سرور واسط) به API متصل شوید. برای مرور مکانیزم‌های امنیتی سرور و فایروال، مقالات مربوط به امنیت سرور توصیه می‌شود.

لایه پنجم — مدیریت پاسخ، timeout و rate limit

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

Timeout و انتخاب مقدار مناسب

مقدار پیش‌فرض timeout در wp_remote_* برابر ۵ ثانیه است. اگر API مقصد کند پاسخ بدهد (مثلاً درخواست‌های سنگین یا لحظات شلوغ)، درخواست با خطای cURL 28 شکست می‌خورد. الگوی درست تنظیم timeout:

$response = wp_remote_post(
    $url,
    array(
        'headers' => /* ... */,
        'body'    => /* ... */,
        'timeout' => 30, // ثانیه
    )
);

if ( is_wp_error( $response ) ) {
    $error_code    = $response->get_error_code();
    $error_message = $response->get_error_message();
    error_log( 'API Error: ' . $error_code . ' - ' . $error_message );
    return false;
}

نکته مهم: تفاوت میان timeout که کل زمان درخواست را محدود می‌کند و connect_timeout که فقط زمان اتصال را محدود می‌کند. اگر سرور مقصد سالم است ولی پاسخ کند می‌دهد، connect_timeout کوتاه و timeout بلندتر تنظیم کنید. اما برای endpointهای همیشه سنگین، به‌جای افزایش بی‌پایان timeout، رویکرد بهتری وجود دارد: کار را در پس‌زمینه انجام دهید.

مدیریت صحیح پاسخ

بعد از دریافت پاسخ، باید سه چیز را جداگانه بررسی کنید: کد وضعیت، بدنه پاسخ، و پیام خطای احتمالی در بدنه. حتی اگر کد وضعیت 200 باشد، ممکن است API در بدنه پیام خطا برگردانده باشد. الگوی درست:

$response = wp_remote_get( $url, $args );

if ( is_wp_error( $response ) ) {
    // خطای شبکه یا timeout
    return array( 'success' => false, 'error' => $response->get_error_message() );
}

$status_code = wp_remote_retrieve_response_code( $response );
$body        = wp_remote_retrieve_body( $response );
$data        = json_decode( $body, true );

if ( 200 !== $status_code ) {
    $api_message = isset( $data['message'] ) ? $data['message'] : 'Unknown error';
    return array( 'success' => false, 'error' => $api_message, 'code' => $status_code );
}

return array( 'success' => true, 'data' => $data );

نکته کلیدی: هرگز به کد وضعیت 200 تکیه نکنید و بدنه را بی‌بررسی مصرف نکنید. بعضی APIها حتی در خطا، کد 200 با بدنه { "error": "..." } برمی‌گردانند. همیشه بدنه را پارس کنید و اگر فیلد error یا errors داشتید، آن را به‌عنوان خطا تلقی کنید. این نکته در پروژه‌های واقعی تفاوت بین یک افزونه کاربلد و یک افزونه آماتور است.

Rate limit و مدیریت صف درخواست‌ها

اگر در بازه کوتاه تعداد زیادی درخواست به یک API می‌فرستید، سرور مقصد با کد 429 پاسخ می‌دهد. در این حالت، باید سیستم صف و retry پیاده‌سازی کنید:

function my_plugin_api_request_with_retry( $url, $args, $max_retries = 3 ) {
    $attempt = 0;
    while ( $attempt < $max_retries ) {
        $response = wp_remote_post( $url, $args );
        $status   = wp_remote_retrieve_response_code( $response );
        if ( 429 !== $status ) {
            return $response;
        }
        // exponential backoff
        $delay = pow( 2, $attempt );
        sleep( $delay );
        $attempt++;
    }
    return $response;
}

نکته مهم: sleep در PHP درخواست کاربر را بلاک می‌کند و برای صف طولانی مناسب نیست. اگر صف طولانی دارید، بهتر است درخواست‌ها را در WP-Cron بنویسید یا از یک صف خارجی استفاده کنید. مرور مکانیزم WP-Cron در بافت خطاهای احتمالی در رفع مشکلات cron در وردپرس توصیه می‌شود.

JSON decode و پارس پاسخ

یکی از خطاهای ظریف در این لایه، پارس نادرست پاسخ JSON است. اگر json_decode خطا بدهد، معمولاً به‌دلیل یکی از این سه است: اول، پاسخ JSON نیست (مثلاً HTML صفحه خطا). دوم، پاسخ JSON ناقص است (مثلاً به‌دلیل timeout وسط درخواست). سوم، پاسخ شامل کاراکترهای غیرمجاز است. همیشه خطای parse را چک کنید:

$data = json_decode( $body, true );
if ( JSON_ERROR_NONE !== json_last_error() ) {
    error_log( 'JSON parse error: ' . json_last_error_msg() );
    return false;
}

برای مطالعه مکانیزم JSON و ساختار آن، JSON چیست و چطور داده‌ها را در وب ساختاردهی می‌کند را ببینید.

درخواست موفق، پاسخ ناموفق. گاهی API کد 200 برمی‌گرداند ولی بدنه پیام خطا دارد. اگر به کد وضعیت بسنده کنید، پیام خطای واقعی را از دست می‌دهید و ساعت‌ها دنبال علت می‌گردید.

چک‌لیست دیباگ گام‌به‌گام خطای اتصال

این ترتیبی است که در پروژه‌های واقعی طی می‌کنم. اگر ترتیب را حفظ کنید، از ارزان‌ترین و سریع‌ترین راه به پیچیده‌ترین می‌رسید:

  1. تست درخواست با Postman: قبل از هر کاری، درخواست را با Postman یا cURL از دستگاه خودتان تست کنید. اگر آنجا کار می‌کند، مشکل در وردپرس یا سرور شماست. اگر آنجا هم کار نمی‌کند، مشکل در API مقصد یا اطلاعات احراز هویت شماست. مرور تست REST API با Postman در این گام توصیه می‌شود.
  2. بررسی خطای دقیق در is_wp_error: در کد افزونه، همیشه پیام خطای دقیق را در error_log ثبت کنید. کدهایی مثل cURL 28 یا cURL 6 پیام دقیق‌تری می‌دهند که در تشخیص لایه کمک می‌کند.
  3. فعال‌سازی WP_DEBUG: در wp-config.php مقادیر WP_DEBUG، WP_DEBUG_LOG و WP_DEBUG_DISPLAY را تنظیم کنید. لاگ در wp-content/debug.log نوشته می‌شود.
  4. تست درخواست در کنسول سرور با cURL: در SSH سرور خودتان، دستور curl -v https://api.example.com/endpoint را اجرا کنید. اگر اینجا هم شکست خورد، لایه چهارم (فایروال) مقصر است. اگر اینجا موفق بود، مشکل در کد PHP یا وردپرس شماست.
  5. بررسی کد وضعیت: کد HTTP برگردانده‌شده را ثبت کنید. 401 و 403 به لایه اول، 404 به لایه دوم، 405 به لایه سوم، 429 به لایه پنجم اشاره دارد.
  6. ذخیره پاسخ کامل در لاگ موقت: در حین دیباگ، کل پاسخ را ذخیره کنید (نه فقط کد). بدنه پاسخ می‌تواند اطلاعات مهمی داشته باشد که در headers دیده نمی‌شود.
  7. تست با timeout بیشتر: اگر cURL 28 می‌گیرید، ابتدا با timeout 60 ثانیه تست کنید. اگر موفق شد، مسئله سرعت API مقصد است و باید به راه‌حل پس‌زمینه فکر کنید.
  8. بررسی SSL: اگر cURL 60 یا 77 می‌گیرید، به لایه دوم برگردید و مسیر CA bundle را بررسی کنید.
  9. تست روی محیط محلی و سرور: اگر روی محلی کار می‌کند ولی روی سرور نه، مسئله محیطی است. بررسی کنید آیا ترافیک خروجی سرور بلاک است یا محدودیت‌های هاستینگ دخیل هستند.
  10. غیرفعال کردن افزونه‌های موثر: اگر افزونه‌های امنیتی یا بهینه‌ساز روی سایت دارید، ابتدا موقتاً غیرفعال کنید. اگر مشکل حل شد، در تنظیمات همان افزونه، درخواست‌های افزونه خود را استثنا کنید. الگوی کامل در چگونه افزونه مشکل‌ساز وردپرس را پیدا کنیم آمده است.

این ترتیب در تست و دیباگ پروژه‌های توسعه وردپرس به‌عنوان پروتکل عیب‌یابی معرفی شده است. اگر در حین عیب‌یابی به خطاهای PHP برخوردید، دیباگ کردن کدهای سفارشی وردپرس را ببینید.

پرسش‌های پرتکرار درباره خطای اتصال افزونه به API

این بخش به پرسش‌هایی اختصاص دارد که در انجمن‌ها و تیکت‌های پشتیبانی بیشترین تکرار را دارند و در نتایج جستجو به‌عنوان پاسخ کوتاه ارزشمندند.

چرا درخواست API در محیط محلی کار می‌کند ولی روی سرور نه؟

سه علت اصلی. اول، فایروال سرور ترافیک خروجی را بلاک می‌کند. دوم، SSL certificate سرور قدیمی است و cURL نمی‌تواند اعتبار مقصد را تایید کند. سوم، IP سرور شما در لیست سیاه API مقصد است. تشخیص: از SSH سرور، دستور curl -v https://api.example.com را اجرا کنید. اگر پاسخ نگرفتید، مسئله از سرور است نه از کد.

چرا کلید API من کار نمی‌کند؟

پنج علت رایج. اول، فاصله اضافه یا کاراکتر پنهان در کلید که با کپی/پیست وارد شده. دوم، منقضی شدن کلید که در پنل API قابل بررسی است. سوم، استفاده از کلید تستی در محیط تولید یا برعکس. چهارم، عدم تطابق مجوزهای کلید با درخواست (مثلاً کلید فقط read-only است ولی درخواست write می‌فرستد). پنجم، نبود کلمه Bearer در هدر Authorization که بعضی APIها الزامی می‌کنند.

چرا کد 429 (Too Many Requests) می‌گیرم؟

یعنی در بازه زمانی کوتاه، تعداد درخواست‌های شما از سقف مجاز API عبور کرده است. راه‌حل: اول، سرعت درخواست‌ها را با تاخیر (delay) کنترل کنید. دوم، درخواست‌ها را در WP-Cron زمان‌بندی کنید تا همزمان نباشند. سوم، از caching پاسخ‌ها استفاده کنید تا درخواست‌های تکراری کاهش یابد. چهارم، اگر API پلن تجاری دارد، ممکن است سقف بالاتری ارائه دهد.

چرا درخواست با timeout تمام می‌شود؟

اگر cURL 28 می‌گیرید، یعنی اتصال به سرور مقصد برقرار نشد یا پاسخ در زمان مقرر نیامد. سه راه‌حل: اول، timeout را افزایش دهید (از ۵ به ۳۰ یا ۶۰ ثانیه). دوم، اگر سرور مقصد واقعاً کند است، درخواست را در پس‌زمینه با WP-Cron انجام دهید نه در زمان درخواست کاربر. سوم، بررسی کنید آیا سرور شما ترافیک خروجی را throttle می‌کند که در این صورت باید با هاست تماس بگیرید.

چطور بفهمم مشکل از فایروال سرور است؟

یک تست ساده: از SSH سرور خودتان، دستور curl -v https://api.ipify.org را اجرا کنید. این سرویس IP عمومی سرور شما را برمی‌گرداند. اگر پاسخ گرفتید، ترافیک خروجی باز است. اگر نگرفتید یا پاسخ کند داشت، احتمالاً فایروال درگیر است. برای اطمینان بیشتر، درخواست به چند سرویس عمومی دیگر (مثل https://httpbin.org/get) بفرستید و رفتار را مقایسه کنید.

آیا مشکل SSL ربطی به سایت من دارد یا سرور؟

اگر سایت شما روی HTTPS است و درخواست خروجی به API با خطای SSL مواجه می‌شود، مسئله در سرور شماست نه در سایت. معمولاً به‌دلیل قدیمی بودن فایل CA bundle در سرور است. راه‌حل: یا از پشتیبانی هاست درخواست به‌روزرسانی کنید، یا در کد افزونه مسیر CA معتبر را مشخص کنید. غیرفعال کردن sslverify فقط راه‌حل موقت برای دیباگ است و هرگز نباید روی محیط تولید استفاده شود.

آیا می‌توان چند API را همزمان از یک افزونه صدا زد؟

بله، ولی مراقب باشید. اگر دو درخواست همزمان می‌فرستید، باید از Requests::request_multiple یا از کتابخانه Requests که وردپرس استفاده می‌کند بهره ببرید. در غیر این صورت، درخواست‌ها به‌صورت سریال اجرا می‌شوند و مجموع زمان می‌تواند از timeout بگذرد. راه‌حل دیگر: استفاده از صف و WP-Cron به‌جای اجرای همزمان در زمان درخواست کاربر.

چرا پاسخ API را ناقص می‌گیرم؟

سه علت. اول، timeout وسط دریافت پاسخ — مقدار timeout را افزایش دهید. دوم، محدودیت حجم پاسخ در سرور شما یا سرور مقصد — این مورد معمولاً در پاسخ‌های حجیم رخ می‌دهد. سوم، مشکل charset در کدگذاری — اگر پاسخ شامل متن فارسی است، اطمینان حاصل کنید که utf-8 به‌درستی منتقل می‌شود. راه‌حل سوم: هدر Accept-Charset: utf-8 را در درخواست ارسال کنید و پاسخ را با mb_convert_encoding در صورت نیاز تبدیل کنید.

آیا وجود افزونه امنیتی روی سایت، اتصال به API را می‌شکند؟

ممکن است، ولی نه به‌طور مستقیم. افزونه‌های امنیتی مثل Wordfence که در بهترین افزونه‌های امنیتی وردپرس معرفی شده‌اند، معمولاً ترافیک ورودی را فیلتر می‌کنند. اما اگر روی حالت سختگیرانه باشند و رفتار عجیب افزونه شما را مشکوک تشخیص دهند، ممکن است درخواست‌های خروجی از طریق admin-ajax یا wp-json را محدود کنند. برای تشخیص، افزونه امنیتی را موقتاً غیرفعال کنید و تست را دوباره اجرا کنید.

معماری پایدار برای اتصال مطمئن به API

پس از حل مشکل، ارزش دارد معماری افزونه را طوری تنظیم کنید که این نوع خطا در آینده تکرار نشود. فهرستی از اصول که در پروژه‌های خودم به‌طور منظم رعایت می‌کنم:

  1. لایه wrapper برای درخواست‌ها: همه درخواست‌های API را در یک تابع wrapper جمع کنید که مدیریت خطا، retry، و logging را به‌طور متمرکز انجام دهد. این الگو، تکرار کد و اختلاف رفتار بین درخواست‌ها را حذف می‌کند.
  2. مدیریت خطای متمرکز: برای هر نوع خطا (auth, network, timeout, server) مسیر متفاوتی طراحی کنید. خطای 401 نیاز به refresh توکن دارد، خطای 429 نیاز به retry با تأخیر، و خطای 500 نیاز به لاگ و اطلاع‌رسانی به ادمین.
  3. Caching پاسخ‌ها: برای درخواست‌های read-heavy از set_transient برای کش پاسخ استفاده کنید. این کار تعداد درخواست‌ها را کاهش می‌دهد و اثر rate limit را کم می‌کند.
  4. ذخیره امن کلیدها: کلیدهای API را در wp-config.php به‌عنوان ثابت ذخیره کنید یا در wp_options با پیشوند اختصاصی. هرگز کلید را در کد هاردکد نکنید.
  5. Rotate کردن توکن‌های حساس: برای APIهایی که از OAuth استفاده می‌کنند، توکن را به‌صورت دوره‌ای rotate کنید و از refresh token استفاده کنید.
  6. نظارت بر اتصال: در پیشخوان افزونه، یک صفحه وضعیت بسازید که آخرین وضعیت اتصال به API را نشان دهد. این کار به کاربر اجازه می‌دهد در صورت خطا، سریعتر متوجه شود.
  7. لاگ‌گیری حرفه‌ای: همه درخواست‌های API را با timestamp، endpoint، کد وضعیت و پیام خطا لاگ کنید. اما لاگ‌ها را در جای درست نگه دارید تا فضای دیتابیس پر نشود.
  8. تست روی محیط استجینگ: هر تغییر در لایه API را روی محیط استجینگ با همان API واقعی تست کنید. تفاوت‌های محیطی می‌توانند باعث رفتار متفاوت در تولید شوند. مرور ساخت محیط استجینگ در توسعه وردپرس با محیط لوکال توصیه می‌شود.
  9. فعال‌سازی HTTPS: اگر افزونه شما با API روی HTTP کار می‌کند، حتماً به HTTPS منتقل کنید. برای مرور مکانیزم انتقال امن، HTTPS چیست و چه تفاوتی با HTTP دارد را ببینید.
  10. رعایت استانداردهای کدنویسی: کد امن، خوانا، و بدون هاردکد. مرور اصول کلی در استانداردهای کدنویسی وردپرس چیست و در بافت مسائل امنیتی، امنیت API توصیه می‌شود.

یک نکته از تجربه شخصی در پروژه‌های فروشگاهی: اگر افزونه شما با یک درگاه پرداخت یا سرویس ارسال خارجی کار می‌کند، تست منظم اتصال در ساعت‌های شلوغی توصیه می‌شود. رخ دادن خطای اتصال در ساعت‌های شلوغ، مستقیماً روی نرخ تبدیل اثر می‌گذارد و به‌عنوان یک ریسک تجاری جدی باید مدیریت شود. رویکرد کلی این مدیریت در CRO برای فروشگاه‌های ووکامرس توضیح داده شده است.

سخن پایانی

خطای عدم اتصال افزونه به API، در نگاه اول یکی از مبهم‌ترین انواع خطا در وردپرس است چون بین دو سرور رخ می‌دهد و پیام خطای دقیقی که به کاربر نمایش داده می‌شود، معمولاً هیچ سرنخی به لایه مقصر نمی‌دهد. این خطا در عمل همیشه در یکی از پنج لایه‌ای که در این مقاله بررسی کردیم ریشه دارد: احراز هویت، URL و SSL، پیکربندی درخواست، فایروال سرور، و مدیریت پاسخ یا timeout. ابزار اصلی عیب‌یابی در این بافت، ترکیب سه چیز است: تست مستقیم با Postman برای حذف متغیرهای محیطی، لاگ‌گیری دقیق در کد افزونه، و تست از SSH سرور برای تفکیک مشکل کد از مشکل شبکه. مسیر عیب‌یابی که در چک‌لیست ارائه کردم، همان ترتیبی است که در پروژه‌های واقعی مرا سریع به علت رسانده؛ نکته کلیدی این است که از گام‌های ارزان (تست با Postman، بررسی کد وضعیت) شروع کنید و به گام‌های گران (تست از SSH، بررسی فایروال) برسید. در بلندمدت، معماری درست با لایه wrapper متمرکز، مدیریت خطای تفکیک‌شده، و caching هوشمند، مهم‌تر از هر راه‌حل لحظه‌ای است — چون این معماری است که اجازه نمی‌دهد خطاهای اتصال به باگ‌های مکرر تبدیل شوند و کاربران نهایی را از کار بیندازند.

اگر این خطا را در یک پروژه واقعی تجربه کرده‌اید و به علت غیرمنتظره‌ای برخورده‌اید — مثلاً یک API که فقط روی IPهای ایران بلاک بود، یا یک هاست اشتراکی که ترافیک خروجی به یک دامنه خاص را مسدود می‌کرد، یا سرویس ابری که در پاسخ‌های حجیم، پاسخ را در میانه قطع می‌کرد — خوشحال می‌شوم تجربه‌تان را در دیدگاه‌ها بنویسید. به‌ویژه اگر ترفند خلاقانه‌ای برای تشخیص سریع‌تر پیدا کرده‌اید، آن تجربه برای نفر بعدی که با همین خطا روبرو می‌شود، ارزشمندتر از هر مستند رسمی است. 🌐