مستندات توسعه‌دهنده

یک درخواست HTTP، و پیامک شما رفته است

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

در یک نگاه

اگر فقط ۳۰ ثانیه وقت دارید، همین چهار خط کافی است.

آدرس پایهhttps://api.payampal.com/api
احراز هویتx-api-key: pp_…
تنها اندپوینت شماPOST /sms/send
تعرفه۲۶۰ تومان برای هر پیامک
مهم‌ترین نکته‌ی این صفحه

پاسخ 201 یعنی «پذیرفته شد و از کیف‌پول کم شد» — نه «به دست گیرنده رسید». تحویل بعداً و به‌صورت غیرهم‌زمان انجام می‌شود، و اگر ارسال برای همیشه شکست بخورد مبلغ خودکار برمی‌گردد. جزئیات در چرخه‌ی عمر پیام.

شروع سریع

این پنج قدم به‌ترتیب‌اند؛ جابه‌جا کردنشان یعنی اولین ارسال شما شکست می‌خورد.

  1. حساب بسازیددر پنل کاربری شماره‌ی موبایل را وارد کنید و با کد پیامکی وارد شوید. رمزی در کار نیست.
  2. کیف‌پول را شارژ کنیداز تب «کیف‌پول». با موجودی صفر هیچ پیامکی ارسال نمی‌شود و پاسخ insufficient balance می‌گیرید.
  3. کلید API بسازیداز تب «کلیدها». متن کلید فقط همان یک بار نمایش داده می‌شود؛ همان‌جا در جای امن ذخیره‌اش کنید.
  4. قالب را انتخاب یا بسازیداز تب «قالب‌ها»، یا از فهرست قالب‌های عمومی. قالبی که «وصل‌نشده» است هنوز قابل ارسال نیست — چه عمومی، چه اختصاصیِ خودتان.
  5. یک پیامک واقعی بفرستید و با چشم ببینیدبه شماره‌ی خودتان. تا وقتی پیامک روی گوشی نیامده، اتصال را تمام‌شده حساب نکنید.

کلید API

کلید در هدر x-api-key می‌رود — نه در Authorization، نه در query string.

header
x-api-key: pp_OzZk6QvR2mNbXwT8yLhP4dCsJfA1gEuK
  • کلید با pp_ شروع می‌شود و در مجموع ۳۵ کاراکتر است.
  • ما فقط هشِ کلید را نگه می‌داریم، پس اگر گمش کنید قابل بازیابی نیست — باید کلید تازه بسازید.
  • هر کلید را می‌توان از پنل باطل کرد؛ ابطال بلافاصله اثر می‌کند.
  • کلید را در مخزن گیت کامیت نکنید. متغیر محیطی بگذارید.
کلید یعنی پول

هر کسی که کلید شما را داشته باشد می‌تواند از کیف‌پول شما پیامک بفرستد. کلید فقط باید روی سرور بماند — هرگز داخل اپلیکیشن موبایل، جاوااسکریپت سمت مرورگر یا کد فرانت‌اند نگذاریدش.

ارسال پیامک

POST /sms/send — تنها اندپوینتی که با کلید API باز می‌شود.

بدنه‌ی درخواست

فیلدنوعالزامیتوضیح
templatestringبلهکد قالب، مثل verify. بین ۱ تا ۶۴ کاراکتر.
mobilestringبلهدقیقاً با الگوی 09XXXXXXXXX — یازده رقم، بدون +98 و بدون فاصله.
parametersobject | arrayخیرمقدار جای‌گذاری‌شده در #PARAM#های قالب. هر دو شکل زیر پذیرفته می‌شود.

دو شکل پارامتر، یک نتیجه

شکل اول ساده‌تر است. شکل دوم همان بدنه‌ای است که کلاینت‌های sms.ir از قبل می‌سازند و عمداً پشتیبانی می‌شود تا مهاجرت به تغییر بدنه نیاز نداشته باشد. هزینه و خروجی هر دو یکسان است.

object — recommended
{
  "template": "verify",
  "mobile": "09121110001",
  "parameters": { "CODE": "45678" }
}
array — sms.ir compatible
{
  "template": "verify",
  "mobile": "09121110001",
  "parameters": [
    { "name": "CODE", "value": "45678" }
  ]
}
بزرگی و کوچکی حروف پارامتر مهم است

CODE و code دو چیز متفاوت‌اند. اگر نام پارامتر با آنچه در قالب تعریف شده یکی نباشد، جای آن پر نمی‌شود و کاربر متن خام #CODE# را دریافت می‌کند. نام دقیق هر پارامتر در پنل، کنار همان قالب نوشته شده است.

پاسخ موفق

201 Created
{
  "id": "efb20be8-c5b5-4427-ae9a-6f7e83df43b3",
  "status": "queued",
  "segments": 1,
  "encoding": "UCS2",
  "cost": 260
}
فیلدیعنی چه
idشناسه‌ی پیام. آن را ذخیره کنید؛ تنها راه پیگیری بعدی همین است.
statusدر لحظه‌ی پاسخ همیشه queued است.
segmentsاین پیام چند پیامک حساب شده. می‌تواند بیشتر از ۱ باشد.
encodingUCS2 برای متن فارسی، GSM7 برای متن کاملاً لاتین.
costتومانی که همین حالا از کیف‌پول کم شد.

قالب‌های عمومی

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

این محدودیت از سمت اپراتور می‌آید، نه ما: هر متنی که به مشترک می‌رسد باید از قبل ثبت و تأیید شده باشد. در عوض، ارسال از طریق قالب سریع است و نیاز به تأییدِ موردی ندارد.

قالب‌های زیر برای همه‌ی مشتری‌ها در دسترس‌اند. اگر متنِ دیگری لازم دارید، از پنل کاربری → تب «قالب‌ها» قالبِ اختصاصیِ خودتان را بسازید؛ بعد از اینکه ما آن را به اپراتور وصل کردیم، دقیقاً مثل بقیه کار می‌کند.

کد تأیید (OTP)

verify
آماده
کد تأیید شما در #COMPANY#: #CODE#
پارامترها
CODECOMPANY
نمونه‌ی خروجی
کد تأیید شما در آرین اسپرت: 45678
هزینه‌ی این نمونه
۲۶۰ تومان (۳۳ کاراکتر · ۱ پیامک · UCS2)

ثبت سفارش

order-placed
آماده
#COMPANY# سفارش شما با شماره #ORDERID# ثبت شد.
پارامترها
ORDERIDCOMPANY
نمونه‌ی خروجی
آرین اسپرت سفارش شما با شماره A-1024 ثبت شد.
هزینه‌ی این نمونه
۲۶۰ تومان (۴۴ کاراکتر · ۱ پیامک · UCS2)

ارسال سفارش

order-shipped
آماده
#COMPANY# سفارش #ORDERID# ارسال شد.
پارامترها
ORDERIDCOMPANY
نمونه‌ی خروجی
آرین اسپرت سفارش A-1024 ارسال شد.
هزینه‌ی این نمونه
۲۶۰ تومان (۳۳ کاراکتر · ۱ پیامک · UCS2)

فهرست نمونه — برای دیدن فهرست کامل و به‌روز، وارد پنل کاربری شوید.

چطور از یک قالب استفاده کنیم

کدِ قالب را در فیلد template بگذارید و هر #PARAM# را با یک کلید هم‌نام در parameters پر کنید. مثلاً برای verify با متنِ کد تأیید شما در #COMPANY#: #CODE#:

request
{
  "template": "verify",
  "mobile": "09121110001",
  "parameters": {
    "COMPANY": "آرین اسپرت",
    "CODE": "45678"
  }
}

متنی که به گیرنده می‌رسد می‌شود: کد تأیید شما در آرین اسپرت: 45678

پارامتری که پر نشود، خام مخابره می‌شود

اگر کلیدی را جا بیندازید یا نامش را اشتباه بنویسید، جای آن پر نمی‌شود و کاربر عبارتِ #CODE# را همان‌طور دریافت می‌کند. نامِ پارامترها به بزرگی و کوچکیِ حروف حساس است.

طولِ پارامتر روی هزینه اثر دارد

هزینه از متنِ نهایی حساب می‌شود، نه از خودِ قالب. یک نامِ شرکتِ بلند یا لینکِ طولانی می‌تواند همان قالبِ یک‌پیامکی را دوپیامکی کند. با محاسبه‌گر بخش بعد بلندترین حالتِ ممکن را امتحان کنید.

هزینه و بخش‌بندی

هزینه ثابت نیست. از روی متن نهایی — قالب به‌علاوه‌ی پارامترهای شما — در لحظه‌ی ارسال حساب می‌شود.

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

متنکدگذارییک پیامکوقتی چندبخشی شد
فارسی (یا هر نویسه‌ی غیرلاتین)UCS2۷۰ کاراکترهر ۶۷ کاراکتر
فقط لاتینGSM7۱۶۰ کاراکترهر ۱۵۳ کاراکتر

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

محاسبه‌گر هزینه

متن نهایی را — همان‌طور که گیرنده می‌بیند، با پارامترهای جای‌گذاری‌شده — این‌جا بنویسید.

کدگذاریUCS2
کاراکتر۱۹
تعداد پیامک۱
هزینه۲۶۰ تومان

با ۵۲ کاراکتر دیگر، این پیام ۲ پیامک حساب می‌شود. · تعرفه ۲۶۰ تومان (تعرفه‌ی پیش‌فرض)

همیشه به cost پاسخ نگاه کنید

لازم نیست این ریاضی را در کد خودتان تکرار کنید. هر پاسخ موفق، segments و cost واقعی همان ارسال را برمی‌گرداند؛ اگر می‌خواهید مصرف را لاگ کنید، از همان استفاده کنید.

چرخه‌ی عمر پیام

بین «پذیرفتیم» تا «رسید» فاصله هست، و این فاصله برای شما معنا دارد.

وضعیتیعنی چهکیف‌پول
queuedپذیرفته شد و در صف ارسال است. پاسخ اولیه همیشه همین است.کم شده
sentبه مخابرات تحویل داده شد.کم شده
failedارسال بعد از چند تلاش برای همیشه شکست خورد.برگشت خورد

ارسال ناموفق تا سه بار با فاصله‌ی فزاینده تکرار می‌شود. اگر همه‌ی تلاش‌ها شکست بخورد، پیام failed می‌شود و مبلغش خودکار به کیف‌پول برمی‌گردد — لازم نیست کاری بکنید و نباید خودتان دوباره تلاش کنید.

وضعیت هر پیام، متن نهایی و هزینه‌اش را در پنل کاربری، تب «پیامک‌ها» می‌بینید و می‌توانید بر اساس شماره جست‌وجو کنید.

درخواست تکراری، پیامک تکراری

این API کلید یکتاسازی (idempotency key) ندارد. اگر به‌خاطر timeout یا خطای شبکه همان درخواست را دوباره بفرستید، دو پیامک ارسال و دو بار هزینه کم می‌شود.

اگر پاسخی دریافت نکردید، کورکورانه retry نکنید: نتیجه را در تب «پیامک‌ها» ببینید، یا خودتان قبل از ارسال مجدد یک قفل بگذارید.

خطاها

همه‌ی پاسخ‌های زیر عیناً از خود API گرفته شده‌اند.

کدپیامعلت و راه‌حل
401missing x-api-keyهدر اصلاً فرستاده نشده.
401invalid api keyکلید اشتباه است یا باطل شده. کلید تازه بسازید.
400invalid mobile numberشماره با الگوی 09XXXXXXXXX نمی‌خواند. +98، فاصله و خط تیره را حذف کنید.
400insufficient balanceموجودی کافی نیست. هیچ پیامکی ارسال نشده و چیزی هم کم نشده.
400parameters must be an object…parameters نه شیء است نه آرایه‌ی {name,value}. احتمالاً رشته فرستاده‌اید.
404template "x" not foundکد قالب اشتباه است، یا قالب اختصاصیِ شخص دیگری است، یا غیرفعال شده.
201{ id, status, segments, cost }پذیرفته و از کیف‌پول کم شد. تحویل هنوز انجام نشده.

بدنه‌ی خطاها همیشه همین شکل است:

error body
{
  "message": "insufficient balance",
  "error": "Bad Request",
  "statusCode": 400
}

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

نمونه کد

هر نمونه یک کار می‌کند: ارسال کد تأیید و خواندن هزینه‌ی واقعی از پاسخ.

cURL
curl -X POST https://api.payampal.com/api/sms/send \
  -H "x-api-key: $PAYAMPAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"template":"verify","mobile":"09121110001","parameters":{"CODE":"45678"}}'
Node.js
async function sendOtp(mobile, code) {
  const res = await fetch('https://api.payampal.com/api/sms/send', {
    method: 'POST',
    headers: {
      'x-api-key': process.env.PAYAMPAL_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      template: 'verify',
      mobile,
      parameters: { CODE: code },
    }),
  });

  const body = await res.json();

  if (!res.ok) {
    // message is a string, or an array for validation errors
    const msg = Array.isArray(body.message) ? body.message.join(', ') : body.message;
    throw new Error(`payampal ${res.status}: ${msg}`);
  }

  // 201 means accepted and charged — not delivered
  return body; // { id, status, segments, encoding, cost }
}
PHP
function sendOtp(string $mobile, string $code): array {
    $ch = curl_init('https://api.payampal.com/api/sms/send');
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => [
            'x-api-key: ' . getenv('PAYAMPAL_API_KEY'),
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS => json_encode([
            'template'   => 'verify',
            'mobile'     => $mobile,
            'parameters' => ['CODE' => $code],
        ], JSON_UNESCAPED_UNICODE),
    ]);

    $raw    = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    $body = json_decode($raw, true);
    if ($status >= 400) {
        $m = $body['message'] ?? 'unknown';
        throw new \RuntimeException('payampal: ' . (is_array($m) ? implode(', ', $m) : $m));
    }
    return $body;
}
Python
import os, requests

def send_otp(mobile: str, code: str) -> dict:
    r = requests.post(
        "https://api.payampal.com/api/sms/send",
        headers={"x-api-key": os.environ["PAYAMPAL_API_KEY"]},
        json={
            "template": "verify",
            "mobile": mobile,
            "parameters": {"CODE": code},
        },
        timeout=15,
    )

    if not r.ok:
        m = r.json().get("message", "unknown")
        raise RuntimeError(f"payampal {r.status_code}: {m}")

    # 201 — accepted and charged, delivery happens after this
    return r.json()

مهاجرت از sms.ir

اگر پروژه‌تان الان مستقیم به sms.ir وصل است، سه چیز عوض می‌شود و بس.

قبل — sms.irبعد — پیام‌پال
آدرسapi.sms.ir/v1/send/verifyapi.payampal.com/api/sms/send
هدر کلیدx-api-keyx-api-key همان
شناسه‌ی قالبtemplateId: 783969template: "verify"
شمارهmobilemobile همان
پارامترها[{name, value}][{name, value}] همان

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

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

هزینه از روی متن قالبی که در پیام‌پال ثبت شده حساب می‌شود، ولی چیزی که مخابره می‌شود متن قالب واقعی سمت اپراتور است. اگر این دو یکی نباشند، محاسبه‌ی هزینه غلط می‌شود — بی‌آنکه خطایی ببینید. پس هنگام ثبت قالب، متن دقیق را کپی کنید؛ با همان فاصله‌ها و نیم‌فاصله‌ها.

مرزهای کلید API

کلید API فقط برای ارسال است. بقیه‌ی کارها از پنل انجام می‌شود.

کاربا کلید APIاز پنل کاربری
ارسال پیامکبله
دیدن موجودیخیرتب کیف‌پول
شارژ کیف‌پولخیرتب کیف‌پول
تاریخچه و وضعیت پیام‌هاخیرتب پیامک‌ها
ساخت و مدیریت قالبخیرتب قالب‌ها
پس موجودی را چطور بپاییم؟

فعلاً به‌صورت برنامه‌نویسی نمی‌شود. تا وقتی اندپوینتش اضافه شود، عملی‌ترین کار این است که خطای insufficient balance را در کدتان جدی بگیرید: آن را لاگ کنید و به تیم خودتان هشدار بدهید تا کیف‌پول قبل از خالی شدن شارژ شود.

پیش از انتشار

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

  • پیامک واقعی را روی گوشی دیده‌امپاسخ ۲۰۱ به‌تنهایی هیچ چیزی درباره‌ی تحویل نمی‌گوید.
  • کلید در متغیر محیطی است، نه در کدو در تاریخچه‌ی گیت هم کامیت نشده.
  • نام پارامترها با قالب مو به مو یکی استبزرگی و کوچکی حروف را هم چک کرده‌ام.
  • متن را با بلندترین پارامتر ممکن حساب کرده‌امبلندترین لینک، بلندترین نام — تا هزینه‌ی واقعی غافلگیرم نکند.
  • خطای insufficient balance را جداگانه هندل کرده‌اماین خطا یعنی «شارژ کن»، نه «دوباره تلاش کن».
  • retry خودکار و کورکورانه ندارمچون هر تلاش دوباره، یک پیامک دیگر و یک هزینه‌ی دیگر است.
  • id هر ارسال را ذخیره می‌کنمبرای وقتی که باید یک ارسال مشخص را پیگیری کنیم.

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

ورود به پنل کاربری