ثبت دستگاه برای اعلان موبایل
ثبت توکن دستگاه برای پوش موبایل، پوش مرورگر و پیامرسانها.
دو مسیر، و اینکه کدام را لازم دارید
ثبت دستگاه یک رویداد نیست. رویدادها از صف رد میشوند چون پلتفرم باید انفجار صدهزارتا در ثانیه را جذب کند. ثبت دستگاه شکل مخالف دارد: چند بار در روز برای هر نصب، و باید بلافاصله خواندنی باشد. کسی که اپ را باز میکند و دو ثانیه بعد وارد یک سناریوی خوشآمد میشود، همان لحظه باید قابل ارسال باشد. برای همین POST /v1/devices مستقیم روی پایگاه داده مینویسد و اصلا وارد صف نمیشود.
دو مسیر برای رسیدن به این نقطه هست و هیچکدام جای دیگری را نمیگیرد.
| مسیر | برای چه کسی | چه چیزی را خودتان پر میکنید |
|---|---|---|
| SDK اندروید | اپ اندروید | فقط توکن، و اگر بخواهید has_gms |
POST /v1/devices | iOS، وب، دسکتاپ، اتصال سمت سرور، و هر اپی که وابستگی اضافه نمیکند | تمام فیلدها |
مسیر دستی حذف نمیشود. SDK منتشرشدهای برای iOS وجود ندارد، یک اتصال سمت سرور اصلا دستگاهی ندارد که از آن توکن بگیرد، و بعضی مشتریها هیچ کتابخانهای به اپشان اضافه نمیکنند. هر سه حالت به همین یک اندپوینت میرسند.
هر دو مسیر روی هاست ورودی کار میکنند: https://in.segmentic.net با کلید نوشتن wk_seg_.... کلید نوشتن عمومی است و قرار است داخل بسته اپ شما باشد. هاست مدیریت (https://api.segmentic.net، با کلید API sk_seg_...) هیچ مسیر دستگاهی ندارد: نه ساخت، نه ویرایش، نه حذف، و نه خواندن. تنها جایی که دستگاههای یک نفر دیده میشوند پنل است، و در خواندن دستگاههای یک پرونده توضیح داده شده.
روی نصب لوکال، همین آدرسها روی http://localhost:8080 بالا میآیند.
ثبت دستگاه شمارش و سهمیه ندارد. overQuota و شمارنده مصرف فقط روی /v1/track و خانوادهاش و /v1/batch صدا زده میشوند؛ POST /v1/devices هیچکدام را صدا نمیزند. تعداد دفعاتی که دستگاههایتان را دوباره ثبت میکنید روی صورتحساب اثری ندارد.
مسیر SDK اندروید
توکن را خود اپ شما میدهد، SDK آن را نمیگیرد. این تصمیم عمدی است: اپی که پوش میفرستد از قبل فایربیس یا بازار یا مایکت را با پروژه و نسخه خودش وصل کرده. اگر SDK هم خودش توکن میگرفت، یعنی نسخه فایربیس را به جای شما انتخاب کرده بودیم و با نسخه خودتان تصادم میکردیم. نتیجهاش این است که کل SDK اندروید هیچ وابستگی ندارد: نه کتابخانه JSON، نه HTTP، نه AndroidX، نه فایربیس. تنها مجوزی که به اپ اضافه میشود INTERNET است.
یک بار راهاندازی، بعد ثبت از داخل همان جایی که توکن میرسد:
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
Segmentic.init(
this,
SegmenticOptions(
writeKey = "wk_seg_...",
apiHost = "https://in.segmentic.net",
),
)
}
}
class MyMessagingService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
Segmentic.registerDevice(mapOf(PushTransport.FCM to token))
}
}
چند مسیر روی یک دستگاه، که کدامشان تحویل میدهد سمت سرور تصمیم میشود:
Segmentic.registerDevice(
mapOf(
PushTransport.FCM to fcmToken,
PushTransport.BAZAAR to bazaarToken,
),
)
PushTransport روی اندروید چهار ثابت دارد: FCM، BAZAAR، MYKET، MQTT. ثابت APNS روی اندروید وجود ندارد، چون آندروید هرگز نمیتواند به آن تحویل بدهد.
اگر اپ شما از قبل به play-services-base وابسته است، جواب قطعی را خودتان بدهید. SDK وقتی چیزی به آن ندهید بسته com.google.android.gms را پروب میکند: پیدا شد true، پیدا نشد false، هر خطای دیگر null یعنی «نمیدانم».
val gms = GoogleApiAvailability.getInstance()
.isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS
Segmentic.registerDevice(tokens, hasGms = gms)
SDK اینها را خودش پر میکند و شما لازم نیست: device_id، platform، has_gms، push_enabled، app_version، manufacturer، model، os_name، os_version، locale، timezone، sdk_name، sdk_version. هویت (user_id و anonymous_id) را هم هسته پر میکند نه فراخوان، تا اپ میزبان نتواند دستگاه را به نام کاربری ثبت کند که از حساب خارج شده.
device_id یک UUID تصادفی در حافظه خصوصی خود اپ است. عمدا نه شناسه تبلیغاتی است و نه Settings.Secure.ANDROID_ID؛ هر دوی آنها آدم را بین اپهای بیربط دنبال میکنند و این سوالی است که مشتری باید جوابش را بدهد نه ما. این شناسه تا وقتی زنده است که اپ حذف یا دادهاش پاک نشود.
این دقیقا بایتهایی است که یک گوشی اندروید ۱۵ روی سیم گذاشت. فایل نمونه دستنویس نیست؛ با یک collector ضبطکننده گرفته شده و فقط از اجرای دوباره روی دستگاه واقعی بازتولید میشود:
{"device_id":"79a1c2c3-a61a-4816-a355-f3d5a0c7ffc2","platform":"android","anonymous_id":"711faad0-317b-40aa-81d7-253a39280348","tokens":{"fcm":"scripted-token-not-a-real-one"},"has_gms":true,"push_enabled":false,"app_version":"0.1.0","manufacturer":"Google","model":"sdk_gphone64_x86_64","os_name":"android","os_version":"15","locale":"en-US","timezone":"Asia/Tehran","sdk_name":"segmentic-android","sdk_version":"0.1.0"}
registerDevice سه نتیجه دارد و تفاوتشان مهم است:
REGISTERED: ذخیره شد.REFUSED: یک4xxگرفت. بدنه دور ریخته میشود، چون همان بدنه دفعه بعد هم به همان شکل رد میشود و نگه داشتنش یعنی هر بار بالا آمدن اپ، برای همیشه، همان درخواست ردشده. همین مقدار وقتی هم برمیگردد که کاربر انصراف داده باشد، و آن حالت اصلا درخواستی نمیفرستد.PENDING: هر چیز دیگر. روی دیسک نوشته میشود و در هر flush و هر بالا آمدن بعدی اپ دوباره تلاش میشود.
این سه مقدار را خودتان تحویل نمیگیرید. Segmentic.registerDevice روی نخ شبکه خودش اجرا میشود و چیزی برنمیگرداند؛ نتیجه فقط در لاگکت با تگ segmentic میآید. هسته است که مقدار را برمیگرداند و خودش هم آن را دوباره تلاش میکند.
فراخوانی دوباره registerDevice برای همان دستگاه تکراری حساب نمیشود؛ سرور روی device_id جایگزینی میکند. هر بار که ارائهدهنده توکن را عوض کرد دوباره صدایش بزنید.
جزئیات نصب و بقیه سطح SDK در SDK اندروید است.
ثبت دستگاه با POST /v1/devices
یک درخواست، یک دستگاه. اندپوینت دستهای وجود ندارد.
curl -X POST https://in.segmentic.net/v1/devices \
-H "Authorization: Bearer wk_seg_..." \
-H "Content-Type: application/json" \
-d '{
"device_id": "dev-1",
"user_id": "u_123",
"platform": "android",
"tokens": { "fcm": "fcm-tok", "bazaar": "bazaar-tok" },
"model": "Xiaomi Redmi Note 12",
"timezone": "Asia/Tehran"
}'
پاسخ 200، عینا همین و نه چیز بیشتری، چون warnings وقتی خالی است اصلا در بدنه نمیآید:
{"status":"ok"}
آیفون همان اندپوینت است با پلتفرم و مسیر دیگر:
curl -X POST https://in.segmentic.net/v1/devices \
-H "Authorization: Bearer wk_seg_..." \
-H "Content-Type: application/json" \
-d '{
"device_id": "F7A1C2C3-A61A-4816-A355-F3D5A0C7FFC2",
"user_id": "u_9137",
"platform": "ios",
"tokens": { "apns": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2" },
"push_enabled": true,
"app_version": "3.4.0",
"model": "iPhone13,2",
"os_name": "ios",
"os_version": "17.4",
"locale": "fa-IR",
"timezone": "Asia/Tehran"
}'
{"status":"ok"}
کلید نوشتن را سه جور میشود داد و هر سه معتبرند:
Authorization: Bearer wk_seg_...X-Segmentic-Key: wk_seg_...- پارامتر کوئری
?write_key=wk_seg_...(برای فراخوانهایی مثلsendBeaconکه هدر نمیتوانند بگذارند)
نکتههای ترابری: بدنه JSON است و بدون توجه به Content-Type پارس میشود؛ سقف بدنه ۵ مگابایت (5 << 20 بایت) است؛ پاسخ همیشه application/json; charset=utf-8 است. CORS باز است (Access-Control-Allow-Origin: *، متدهای POST, OPTIONS، هدرهای Content-Type, Authorization, X-Segmentic-Key، عمر پیشپرواز 86400) ولی credentials هرگز اجازه داده نمیشود.
tenant_id و app_id اگر در بدنه بفرستید نادیده گرفته میشوند. هر دو از کلید نوشتن میآیند. تستی هست که "tenant_id": 999 میفرستد و بررسی میکند دستگاه هنوز زیر مستاجر واقعی کلید ذخیره شده باشد.
هر فیلد بدنه
| فیلد | نوع | اجباری | رفتار و پیشفرض |
|---|---|---|---|
device_id | رشته | بله | فاصلههای دو سر حذف میشود. خالی یا بلندتر از 256 بایت کل درخواست را رد میکند |
platform | رشته | بله | با بیتفاوتی به بزرگی و کوچکی حروف و با نامهای مستعار پارس میشود. جدول پلتفرمها |
user_id | رشته | یکی از این دو لازم است | فاصلهگیری، سپس برش در 256 بایت |
anonymous_id | رشته | یکی از این دو لازم است | فاصلهگیری، سپس برش در 256 بایت |
tokens | شیء، نگاشت نام مسیر به توکن | نه، ولی قانون «چیزی برای ذخیره نماند» را ببینید | کلیدها کوچک و فاصلهگیری میشوند، مقدارها فاصلهگیری. سقف هر توکن 4096 بایت |
push_provider | رشته | نه | شکل قدیمی تکمسیره، جفت با push_token |
push_token | رشته | نه | شکل قدیمی تکمسیره |
has_gms | بولین یا null | نه | سهحالته. نبودنش یعنی نامعلوم، که false نیست |
push_enabled | بولین یا null | نه | سهحالته. نبودنش یعنی نامعلوم، که «مجاز» خوانده میشود |
app_version | رشته | نه | فاصلهگیری، برش در 256 بایت |
manufacturer | رشته | نه | فاصلهگیری، برش در 256 بایت |
model | رشته | نه | فاصلهگیری، برش در 256 بایت |
os_name | رشته | نه | فاصلهگیری، برش در 256 بایت |
os_version | رشته | نه | فاصلهگیری، برش در 256 بایت |
locale | رشته | نه | فاصلهگیری، برش در 256 بایت |
timezone | رشته | نه | فاصلهگیری، برش در 256 بایت. همین است که «ساعت ۹ صبح بفرست» و ساعت سکوت را به ساعت خود گیرنده معنا میکند |
sdk_name | رشته | نه | فاصلهگیری، برش در 256 بایت |
sdk_version | رشته | نه | فاصلهگیری، برش در 256 بایت |
برش روی مرز نویسه انجام میشود، پس یک فیلد فارسی هرگز به UTF-8 نامعتبر تبدیل نمیشود. تست این را با ۲۵۶ نسخه از «ش» میسنجد.
شکل قدیمی تکمسیره هنوز کار میکند و قرار نیست حذف شود، چون مشتریها نسخه SDK را سالها ثابت نگه میدارند و ارتقا نباید شرط دریافتکردن باشد:
curl -X POST https://in.segmentic.net/v1/devices \
-H "Authorization: Bearer wk_seg_..." \
-H "Content-Type: application/json" \
-d '{
"device_id": "dev-old",
"user_id": "u_123",
"platform": "android",
"push_provider": "bazaar",
"push_token": "legacy"
}'
{"status":"ok"}
اگر هر دو شکل را بفرستید، اول جفت قدیمی اعمال میشود و بعد tokens رویش مینویسد، پس نگاشت غنیتر برنده است.
قانون «چیزی برای ذخیره نماند»
tokens خالی بهتنهایی خطا نیست؛ بستگی به push_enabled دارد.
- بدون توکن و
push_enabledغایب یاtrue: خطای400با پیامdevice: registration carries no usable token. فراخوان هیچ کاری نکرده، پس ذخیرهاش فقط جدول را بزرگ میکرد. - بدون توکن و
push_enabled: false: پاسخ200و ردیف ذخیره میشود. این یک تغییر وضعیت واقعی است: کاربر اعلان را خاموش کرده و باید ثبت شود.
حالت دوم دقیقا همان چیزی است که بعد از خاموشکردن اعلان در تنظیمات سیستم عامل باید بفرستید:
curl -X POST https://in.segmentic.net/v1/devices \
-H "Authorization: Bearer wk_seg_..." \
-H "Content-Type: application/json" \
-d '{
"device_id": "dev-1",
"user_id": "u_123",
"platform": "android",
"push_enabled": false
}'
{"status":"ok"}
پلتفرمها و مسیرهایی که به هرکدام میرسند
platform اجباری است و پیشفرض ندارد. جا انداختنش خطای 400 میدهد.
| پلتفرم | املاهای پذیرفتهشده | مسیرهای پذیرفتهشده هنگام ثبت | کمپین واقعا تحویل میدهد؟ |
|---|---|---|---|
android | android | fcm، bazaar، myket، huawei، mqtt | بله، با fcm یا bazaar یا myket یا huawei |
ios | ios، iphone، ipad | apns، mqtt | بله، با apns |
web | web، browser، webapp | webpush | بله، با webpush |
windows | windows، win، win32، win64 | webpush | نه. مسیر پیشفرضی برایش تعریف نشده |
macos | macos، mac، mac os، osx، darwin | webpush | نه. مسیر پیشفرضی برایش تعریف نشده |
linux | linux | webpush | نه. مسیر پیشفرضی برایش تعریف نشده |
server | server، backend، api | هیچکدام | ثبت با 400 رد میشود |
نامها با بیتفاوتی به بزرگی و کوچکی حروف و بعد از حذف فاصلههای دو سر پارس میشوند؛ " Android " میشود android. همین برای کلیدهای tokens هم صادق است: " FCM " میشود fcm.
سه پلتفرم دسکتاپ عمدا زیر web تا نشدهاند، تا «چند تا نصب مکاواس داریم» جوابی داشته باشد. اصلا هم به این دلیل اضافه شدند که یک مشتری واقعی اینها را داشت و ایمپورتش شکست.
windows، macos و linux ثبت میشوند ولی امروز پوش نمیگیرند. جدول ترتیب مسیرها فقط برای android، ios و web مدخل دارد، و روتر برای پلتفرمی که مدخل ندارد هیچ مسیری تولید نمیکند. نتیجه هنگام ارسال push: no usable transport for this device است.
server رد میشود نه ذخیره. یک بکاند اصلا دستگاهی ندارد، و ردیفی که هیچ پوشی به آن نمیرسد در تمام عددهای «قابل دسترسی» که به مشتری نشان میدهیم شمرده میشد. یعنی platform: "api" یا platform: "backend" خطای 400 میگیرد.
پلتفرم ناشناس و مسیر ناشناس دو رفتار کاملا متفاوت دارند:
- پلتفرم ناشناس (مثلا
blackberryیاsymbian) کل درخواست را رد میکند. هیچچیز ذخیره نمیشود. - مسیر ناشناس (مثلا
pigeon) فقط همان یک توکن را با یک هشدارtransport_not_supportedدور میریزد و بقیه ثبت ادامه پیدا میکند.
مسیر mqtt پذیرفته و ذخیره میشود و هرگز تحویل نمیدهد. ثابتش تعریف شده و در ترتیب پیشفرض هم هست، ولی هیچ پیادهسازی ارائهدهنده MQTT در کل مخزن وجود ندارد، و روتر مسیری را که ارائهدهنده ندارد کنار میگذارد. روی mqtt حساب نکنید.
شکل توکن هر مسیر
| مسیر | چه چیزی بفرستید | پاکسازی سمت سرور |
|---|---|---|
fcm | همان رشتهای که onNewToken داد | فقط فاصلهگیری دو سر |
apns | هگز حروف کوچک | فاصلهگیری، حذف < و > از دو سر، حذف تمام فاصلهها، کوچککردن حروف |
bazaar | توکنی که سرویس پوش کافه بازار داد | فقط فاصلهگیری دو سر |
myket | توکنی که سرویس پوش مایکت داد | فقط فاصلهگیری دو سر |
huawei | توکنی که HMS Push Kit داد | فقط فاصلهگیری دو سر |
webpush | آدرس endpoint اشتراک مرورگر | فقط فاصلهگیری دو سر |
mqtt | پذیرفته میشود، تحویل نمیدهد | فقط فاصلهگیری دو سر |
پاکسازی APNs تزئینی نیست. APIهای قدیمی iOS توکن را به شکل <a1b2 c3d4> رشته میکنند و فرستادن همان، برای همیشه، در هر پیام، از سمت اپل رد میشود؛ و کمپین گزارش میدهد صددرصد ارسال شد. تست این را میسنجد: " <A1B2 C3D4 E5F6> " تبدیل میشود به a1b2c3d4e5f6. توجه کنید این پاکسازی فقط برای مسیر apns انجام میشود.
هیچ اعتبارسنجی دیگری روی شکل توکن انجام نمیشود. تنها دو بررسی این است که بعد از فاصلهگیری خالی نباشد و از 4096 بایت بلندتر نباشد. نه پیشوند، نه مجموعه نویسه، نه بازه طول. توکن بدشکل FCM اینجا پذیرفته میشود و هنگام ارسال از خود گوگل رد میگیرد.
اگر SDK اپل به شما Data میدهد، خودتان به هگز تبدیلش کنید. رشتهکردن با description همان <a1b2 c3d4> را میسازد:
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
let hex = deviceToken.map { String(format: "%02x", $0) }.joined()
register(apnsToken: hex) // your own POST /v1/devices call
}
اجازه اعلان و پلی سرویسز: هر دو سهحالتهاند
نبودن یک فیلد با false بودنش یکی نیست، و این دو فیلد جایی هستند که این تفاوت پول و مخاطب برایتان خرج میکند.
push_enabled اجازه اعلان در سطح سیستم عامل است:
| مقدار در JSON | معنی | اثر روی هدفگیری |
|---|---|---|
فیلد غایب، یا null | نامعلوم؛ SDK آنقدر قدیمی است که گزارش نمیدهد | «مجاز» خوانده میشود |
true | کاربر اجازه داده | مجاز |
false | کاربر اعلان را در تنظیمات خاموش کرده | از هر کمپینی حذف میشود |
نامعلوم عمدا «مجاز» خوانده میشود: ساکتکردن کاربری بهخاطر اینکه نسخه اپش قدیمی است، هر مخاطبی را بیصدا آب میکند. کوئری تحویل شرط AND d.push_enabled دارد و دو ایندکس جستجو هم روی همین شرط جزئیاند، پس حذف دو جا اعمال میشود.
نکته ذخیرهسازی که به آن برمیخورید: ستون push_enabled BOOLEAN NOT NULL DEFAULT TRUE است. یعنی سهحالته موقع نوشتن جمع میشود. روی درج COALESCE($8, TRUE) و روی بهروزرسانی COALESCE($8, devices.push_enabled). نامعلوم برای ردیف تازه true میشود و ردیف موجود را دستنخورده میگذارد، چون سکوت SDK نباید چیزی را که نسخه جدیدتر قبلا گفته پاک کند.
has_gms گزارش SDK از سالم بودن گوگل پلی سرویسز است:
| مقدار در JSON | معنی | اثر |
|---|---|---|
غایب یا null | SDK نگاه نکرده | روتر فرض میکند احتمالا هست و FCM را امتحان میکند |
true | پلی سرویسز سالم به نظر رسید | FCM انتخاب اول |
false | پلی سرویسز نیست | FCM هنگام ارسال کاملا کنار گذاشته میشود، و ثبت هشدار fcm_without_gms میدهد |
توکن FCM حتی وقتی has_gms: false است ذخیره میشود، چون پلی سرویسز بعدا میتواند نصب شود و دور ریختن توکن آن بازیابی را غیرممکن میکرد.
وقتی منظورتان «بررسی نکردم» است، false نفرستید. فیلد را اصلا نگذارید. push_enabled: false دستگاه را از هر کمپینی بیرون میبرد و has_gms: false تا ثبت بعدی که خلافش را بگوید FCM را برای آن دستگاه خاموش میکند.
روی iOS اصلا has_gms نفرستید. آنجا بیمعنی است و SDK آیفون هم نمیفرستد.
هشدارها
هشدار یعنی چیزی را پذیرفتیم ولی تغییرش دادیم. آرایه warnings در پاسخ 200 هم میآید و در پاسخ 400 هم. تنها چهار کد وجود دارد.
code | field | چه وقت | توکن چه میشود |
|---|---|---|---|
empty_token | نام مسیر | توکن خالی یا فقط فاصله بود | دور ریخته میشود |
token_too_long | نام مسیر | توکن از 4096 بایت بلندتر بود | دور ریخته میشود |
transport_not_supported | نام مسیر | مسیر از نظر فیزیکی نمیتواند به آن پلتفرم برسد، یا اصلا نام مسیر ناشناس است | دور ریخته میشود |
fcm_without_gms | fcm | توکن FCM روی دستگاهی که has_gms: false گزارش کرده | نگه داشته میشود |
متن message هرکدام به ترتیب: token was empty and has been ignored، token exceeds the maximum length and has been ignored، transport <t> cannot deliver to <platform>، device reports no Play Services; FCM will not be used for it.
این نمونه واقعی است. یک نصب اندروید که هم توکن APNs فرستاده و هم FCM:
curl -X POST https://in.segmentic.net/v1/devices \
-H "Authorization: Bearer wk_seg_..." \
-H "Content-Type: application/json" \
-d '{
"device_id": "dev-1",
"user_id": "u",
"platform": "android",
"tokens": { "apns": "wrong", "fcm": "right" }
}'
پاسخ 200. توکن قابل استفاده ذخیره شده و آن یکی نه:
{"status":"ok","warnings":[{"code":"transport_not_supported","message":"transport apns cannot deliver to android","field":"apns"}]}
پاسخ 200 را بخوانید، فقط به کدش نگاه نکنید. توکن اشتباه بهآرامی دور ریخته میشود و تنها جایی که این را میفهمید همین آرایه است. تفاوتش این است که مشتری یک هفته دنبال نرخ تحویل صفر بگردد، یا اولین بار که اندپوینت را صدا میزند مشکل را ببیند.
ردها و کدهای وضعیت
پیام 400 عینا متن خطای اعتبارسنجی است، و هشدارها روی شکست هم همراه میآیند. بدون آنها، کسی که از یک بیلد اندروید توکن APNs فرستاده فقط «توکن قابل استفادهای نیست» را میبیند و هیچ سرنخی ندارد.
| وضعیت | message | چه وقت |
|---|---|---|
400 | device: device_id is required | device_id نیامده، بعد از فاصلهگیری خالی است، یا از 256 بایت بلندتر است |
400 | device: platform must be one of android, ios, web, windows, macos, linux | platform نیامده، ناشناس است، یا به server میرسد |
400 | device: user_id or anonymous_id is required | هر دو خالیاند |
400 | device: registration carries no usable token | هیچ توکن قابل استفادهای نماند و اعلان صریحا خاموش اعلام نشده |
400 | malformed JSON | بدنه JSON نیست |
401 | missing write key | هیچ کلیدی در هدر یا کوئری نبود |
401 | invalid write key | کلید ناشناس، باطلشده، یا معلق |
413 | request body too large | بدنه از ۵ مگابایت گذشت |
503 | cannot verify the write key right now; retry | خود جستجوی کلید شکست خورد. با هدر Retry-After: 5 |
503 | temporarily unavailable, please retry | نوشتن دستگاه در پایگاه داده شکست خورد |
405 | (بدون بدنه JSON) | این نصب اصلا انبار دستگاه ندارد، پس مسیر ثبت نشده |
نمونه یک 400 کامل، از تستی که پلتفرم ios را با توکن fcm میفرستد. ترتیب کلیدها همین است: status، بعد warnings، بعد message:
{"status":"error","warnings":[{"code":"transport_not_supported","message":"transport fcm cannot deliver to ios","field":"fcm"}],"message":"device: registration carries no usable token"}
401 را دائمی بخوانید و 503 را موقت، دقیقا همان کاری که SDKها میکنند. جستجوی کلید وقتی خودش خراب است 503 میدهد نه 401، و دلیلش اندازهگیری شده است: وقتی این نقطه 401 جواب میداد، با پایگاه داده خاموش، هشت از هشت رویداد 401 گرفتند، یعنی یک قطعی زیرساخت در سمت مشتری رویداد نابود میکرد در حالی که لاگ خودش میگفت کلید API نامعتبر است.
سه تله که اولین بار توسعهدهنده را زمین میزنند:
device_idبلندتر از256بایت پیامdevice_id is requiredمیگیرد، با اینکه یکی فرستادهاید. پیام گمراهکننده است.POST /v1/devices/unregisterبا بدنه خراب هم دقیقا همانdevice_id is requiredرا میدهد. JSON خرابتان بهعنوان «شناسه نفرستادی» گزارش میشود.405روی/v1/devicesدر محیط استیجینگ یک واقعیت پیکربندی است نه اشکال بدنه شما. وقتی انبار دستگاه وصل نباشد، مسیر اصلا ثبت نمیشود؛ الگویOPTIONS /v1/کل مسیرهای زیر/v1/را ادعا میکند، پس سرور مسیر را میشناسد و متد را نه.
هر پاسخ این اندپوینتها، چه موفق و چه ناموفق، هدر X-Segmentic-Trace دارد: شانزده رقم هگز. در هیچ بدنهای تکرار نمیشود، پس اگر آن را لاگ نکنید از دست میرود، و برای پیگیری یک درخواست تنها چیزی است که به کار ما میآید.
فهرست کامل قرارداد خطاها در خطاها است.
همان دستگاه دوباره، همان توکن جای دیگر
توکن پوش یک مسیر تحویل را میشناسد، نه یک دستگاه را. تمام رفتار این بخش از همین یک جمله میآید.
ثبت دوباره همان دستگاه. ثبت یک upsert روی (tenant_id, device_id) است، و دو قانون در انبار اعمال میشود نه در فراخوان:
- هرگز پاک نکن. فیلدی که SDK نفرستاده مقدار ذخیرهشدهاش را نگه میدارد. هر ستون رشتهای از
COALESCE(NULLIF(EXCLUDED.x, ''), devices.x)رد میشود. SDKها حقایق دستگاه را از چند جا و در زمانهای مختلف گزارش میکنند، پس اگر سکوت را «پاکش کن» میخواندیم، توکن FCM در هر بار باز شدن اپ توکن بازار را پاک میکرد. - توکن را بگیر. اگر نصب دیگری همان توکن را دارد، از دست داده است.
همچنین last_seen_at همیشه جلو میرود، و revoked_at به NULL برمیگردد چون نصب دوباره یک دستگاه باطلشده را زنده میکند. توکنی که بازنشسته شده بود retired_at صفر میشود، چون برگشتن یک توکن بازنشسته یعنی اپ دوباره نصب شده و مسیر زنده است.
همان توکن روی دستگاه دیگر. این مهمترین قانون حذف تکراری اینجاست. بعد از بازیابی از بکاپ یا نصب دوباره، ارائهدهنده میتواند همان توکن را به یک device_id جدید بدهد. اگر هر دو ردیف زنده بمانند، هر کمپین به آن آدم دو بار تحویل میدهد، و این شبیه اشکال در اپ مشتری به نظر میرسد نه در پلتفرم ما. پس ثبت، قبل از درج، هر دارنده دیگر آن توکن را حذف میکند:
DELETE FROM device_tokens
WHERE tenant_id = $1 AND transport = $2 AND token = $3 AND device_id <> $4
این حذف داخل همان تراکنش upsert اجرا میشود، و یک ایندکس یکتا آن را از «تا حد امکان» به «غیرقابل مذاکره» تبدیل میکند:
CREATE UNIQUE INDEX idx_device_tokens_unique
ON device_tokens (tenant_id, transport, token)
WHERE retired_at IS NULL
دو کاربر روی یک گوشی. ردیف دستگاه با نصب کلید میخورد، نه با آدم. یک گوشی در عمرش چند حساب را میبیند و یک آدم چند گوشی دارد؛ اگر با کاربر کلید میخورد، پیامهای حساب قبلی به هرکسی که بعد وارد میشود میرسید. با POST /v1/devices بعدی که user_id تازه دارد، upsert همان user_id را جایگزین میکند: ورود جدیدتر برنده است. جدا کردن حساب قبلی کار خروج از حساب است و اگر آن را صدا نزنید، تا ثبت بعدی حساب قبلی همچنان چسبیده است.
یک کاربر با چند دستگاه. دو جا محدود میشود.
- سقف تعداد نصبهایی که یک نفر روی آنها دریافت میکند: پیشفرض ۵، از
DELIVERY_DEVICES_PER_USER. کسی که پنج بار گوشی عوض کرده هنوز پنج ردیف دارد و بدون سقف همان اعلان را پنج بار میگیرد، که اسپم خوانده میشود و سریعترین راه از دست دادن اجازه پوش است. - نصبهای کهنه کنار گذاشته میشوند: پیشفرض ۱۸۰ روز، از
DELIVERY_STALE_DEVICE.
سقف دستگاه میشمارد نه ردیف توکن. گوشی با سه مسیر یک گیرنده است. ترتیب last_seen_at DESC است، تازهترین نصب اول.
نصب ناشناس. ثبتی که فقط anonymous_id دارد پذیرفته و ذخیره میشود، ولی امروز هدف هیچ کمپینی نمیشود. کوئری تحویل فقط روی d.user_id = ANY($2) انتخاب میکند و هیچ مسیر کدی در انبار وجود ندارد که دستگاه را با anonymous_id بخواند. پس پوش به یک نصب هنوز شناسایینشده وجود ندارد. اگر کمپین خوشآمد برای کاربر ثبتنامنکرده میخواهید، امروز جوابی ندارد؛ باید اول شناسایی اتفاق بیفتد.
خروج از حساب و حذف نصب
خروج از حساب، حذف نصب نیست. دو کار متفاوتاند و یک اندپوینت با یک پرچم آنها را از هم جدا میکند.
این بیش از آنچه به نظر میرسد اهمیت دارد. روی یک گوشی مشترک، چسبیده ماندن حساب قبلی یعنی نفر بعدی بهروزرسانی سفارش یک نفر دیگر را میگیرد؛ از آن اشکالهایی که قرارداد را تمام میکند.
| فیلد | نوع | اجباری | معنی |
|---|---|---|---|
device_id | رشته | بله | نصبی که روی آن عمل میشود |
user_id | رشته | نه | وقتی بیاید، فقط اگر همان کاربر چسبیده باشد جدا میکند |
revoked | بولین | نه، پیشفرض false | false خروج از حساب، true نصب را رفته علامت میزند |
curl -X POST https://in.segmentic.net/v1/devices/unregister \
-H "Authorization: Bearer wk_seg_..." \
-H "Content-Type: application/json" \
-d '{"device_id": "dev-1", "user_id": "u_123"}'
{"status":"ok"}
curl -X POST https://in.segmentic.net/v1/devices/unregister \
-H "Authorization: Bearer wk_seg_..." \
-H "Content-Type: application/json" \
-d '{"device_id": "dev-1", "revoked": true}'
{"status":"ok"}
در پایگاه داده دقیقا این اتفاق میافتد. خروج از حساب:
UPDATE devices SET user_id = '', last_seen_at = now()
WHERE tenant_id = $1 AND device_id = $2
AND ($3 = '' OR user_id = $3)
ردیف عمدا زنده میماند. توکن هنوز معتبر است، آدم ممکن است دوباره وارد شود، و حذف ردیف باعث میشد ثبت بعدی مثل یک نصب کاملا تازه دیده شود، که شمارش نصب و بازگشت را خراب میکند. user_id خالی در درخواست یعنی «هرکه چسبیده را جدا کن».
و حذف نصب:
UPDATE devices SET
revoked_at = now(),
revoked_reason = 'unregistered',
first_uninstalled_at = COALESCE(first_uninstalled_at, now())
WHERE tenant_id = $1 AND device_id = $2 AND revoked_at IS NULL
دلیل unregistered است نه uninstalled، چون این SDK است که unregister را صدا میزند و در عمل خیلی بیشتر خروج از حساب است تا حذف اپ. یکی شمردن این دو، هر خروج از حساب را در گزارش حذف نصب شبیه ریزش نشان میداد.
هیچکدام از این دو، توکنها را بازنشسته نمیکند. دستگاه باطلشده با شرط d.revoked_at IS NULL از ارسال کنار میرود.
پاسخها: 200 با {"status":"ok"}؛ 400 با {"status":"error","message":"device_id is required"} وقتی بدنه JSON نیست یا device_id خالی است؛ 503 با پیام موقت وقتی نوشتن شکست بخورد؛ و همان 401 و 503 احراز هویت بخش قبل.
معادل مرورگر: اشتراک وبپوش
مرورگر توکن ندارد، اشتراک دارد. معادل POST /v1/devices روی وب دو اندپوینت جدا است.
curl -X POST https://in.segmentic.net/v1/webpush/subscribe \
-H "Authorization: Bearer wk_seg_..." \
-H "Content-Type: application/json" \
-d '{
"user_id": "u_9137",
"subscription": {
"endpoint": "https://fcm.googleapis.com/fcm/send/abc123",
"p256dh": "BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U",
"auth": "tBHItJI5svbpez7KI4CCXg"
}
}'
{"status":"ok"}
شکل تخت هم پذیرفته میشود، یعنی endpoint و p256dh و auth در سطح بالا. شکل تودرتو وجود دارد چون همان شکل خود Push API است: صفحه میتواند عینا چیزی را که مرورگر داده پست کند، بدون اینکه بازش کند و مهمتر، بدون اینکه کلیدها را دوباره کدگذاری کند. رشته base64 که یک تابع کمکی خوشنیت رمزگشایی و دوباره کدگذاری کرده، کلاسیکترین راهی است که یک اشتراک بیصدا روی ماشین گیرنده از رمزگشایی میافتد.
| فیلد | نوع | اجباری | معنی |
|---|---|---|---|
user_id | رشته | بله | اشتراک به نام یک آدم ذخیره میشود |
endpoint | رشته | بله | آدرس سرویس پوش این نصب مرورگر. داشتنش برای ارسال کافی است، پس مثل یک راز با آن رفتار میشود و هرگز به کلاینت برنمیگردد |
p256dh | رشته | بله | کلید عمومی مرورگر، base64url، نقطه فشردهنشده P-256 |
auth | رشته | بله | راز مشترک ۱۶ بایتی که مرورگر ساخته |
هر چهار تا لازماند. نبودن هرکدام 400 با {"status":"error","message":"user_id and a complete subscription are required"} میدهد و هیچچیز ذخیره نمیشود: endpoint بدون کلید غیرقابل استفاده است، بدنه رمز نمیشود، و ذخیرهاش بهجای یک اتصال خراب، یک گیرنده همیشهناموفق نشان میداد. هدر User-Agent درخواست هم کنارش ذخیره میشود.
لغو اشتراک فقط endpoint میخواهد:
curl -X POST https://in.segmentic.net/v1/webpush/unsubscribe \
-H "Authorization: Bearer wk_seg_..." \
-H "Content-Type: application/json" \
-d '{"endpoint": "https://fcm.googleapis.com/fcm/send/abc123"}'
{"status":"ok"}
هیچ user_id لازم نیست و هیچکدام بررسی نمیشود، و دلیلش صریح است: endpoint راز خود اشتراک است، داشتنش از قبل برای ارسال به آن مرورگر کافی است، پس خواستن چیز بیشتری قبل از اجازه دادن به قطع دریافت، از جهت اشتباه محافظت میکرد.
کانال وبپوش امروز علاوه بر اشتراک، به یک ردیف دستگاه هم نیاز دارد. توزیعکننده برای کانالهای push و webpush سراغ رجیستری دستگاه میرود و اگر چیزی پیدا نکند، قبل از اینکه فرستنده وبپوش اصلا صدا زده شود، پیام را با دلیل not_reachable کنار میگذارد. اشتراکهای مرورگر در جدول جداگانهای (webpush_subscriptions) ذخیره میشوند، پس بازدیدکنندهای که فقط POST /v1/webpush/subscribe را صدا زده امروز غیرقابل دسترس شمرده میشود. این بررسی شرط ندارد: روی نصبی که اصلا انبار دستگاه وصل نشده هم اجرا میشود، و آنجا هر پوش و هر وبپوش، بدون استثنا، همینجا کنار گذاشته میشود.
راهحل امروزی این است که همان مرورگر را بهعنوان دستگاه هم ثبت کنید، با platform: "web" و endpoint بهعنوان توکن مسیر webpush:
curl -X POST https://in.segmentic.net/v1/devices \
-H "Authorization: Bearer wk_seg_..." \
-H "Content-Type: application/json" \
-d '{
"device_id": "browser-9f31",
"user_id": "u_9137",
"platform": "web",
"tokens": { "webpush": "https://fcm.googleapis.com/fcm/send/abc123" }
}'
{"status":"ok"}
این را با چشم باز بنویسید: هیچ تستی وجود ندارد که کانال وبپوش را از مسیر توزیعکننده بپوشاند. فرستنده وبپوش مستقیم تست شده، ولی مسیر کامل کمپین نه.
بقیه سطح وب، شامل service worker و اینکه چرا باید از ریشه دامنه سرو شود و کلید VAPID را از کجا میگیرید، در SDK وب است.
پیامرسانها: بله، ایتا، روبیکا
شناسه چت یک آدرس نیست، یک نشانه رضایت است. هیچکدام از این سه پلتفرم به یک بات اجازه نمیدهند به کسی پیام بدهد که خودش گفتگو را شروع نکرده. این شناسه را به دو انتخابی نزدیک میکند تا به شماره تلفنی که از یک فرم سفارش برداشته شده.
اهمیتشان هم دلیل سادهای دارد: بخش بزرگی از مخاطب ایرانی روی اینها قابل دسترس است و جای دیگری نه. پوش به پلی سرویسز سالم یا نصب اپ نیاز دارد، پیامک به ازای هر پیام پول میخورد و اپراتور سقف میگذارد، و ایمیل را مصرفکننده ایرانی تقریبا استفاده نمیکند.
curl -X POST https://in.segmentic.net/v1/messenger/link \
-H "Authorization: Bearer wk_seg_..." \
-H "Content-Type: application/json" \
-d '{
"user_id": "u_9137",
"platform": "bale",
"chat_id": "44120099",
"source": "bot_start"
}'
{"status":"ok"}
| فیلد | نوع | اجباری | پیشفرض |
|---|---|---|---|
user_id | رشته | بله | |
platform | رشته | بله، یکی از bale، eitaa، rubika | |
chat_id | رشته | بله | |
username | رشته | نه | ذخیره میشود و هرگز با ثبت بعدی پاک نمیشود |
source | رشته | نه | bot_start |
source ثبت میکند شناسه از کجا آمده. پیشفرضش bot_start است، تنها مسیری که رضایت واقعی دارد؛ هر چیز دیگر ارزش دارد که بعدا بشود پیدایش کرد. کسی که در بات /start زده داستان رضایتش با کسی که شناسهاش از یک فایل CSV آمده یکی نیست.
قطع اتصال فقط user_id و platform میخواهد:
curl -X POST https://in.segmentic.net/v1/messenger/unlink \
-H "Authorization: Bearer wk_seg_..." \
-H "Content-Type: application/json" \
-d '{"user_id": "u_9137", "platform": "eitaa"}'
{"status":"ok"}
ردها: 400 با {"status":"error","message":"user_id, chat_id and a known platform are required"} روی اتصال، و {"status":"error","message":"user_id and a known platform are required"} روی قطع اتصال. پلتفرمی مثل telegram همینجا رد میشود؛ ستون یک CHECK دارد و مقدار ناشناخته در پستگرس با خطایی میشکست که هیچکس نمیتواند رویش کاری بکند.
وصل کردن دوباره تنها راه پاککردن یک بلاک است: blocked_at و blocked_reason صفر میشوند و هیچچیز دیگری آنها را پاک نمیکند. تلاش خودکاری که تصمیم بگیرد بلاک منقضی شده، پیام دادن به کسی است که رفته.
هیچ وبهوکی برای پیامهای بات وجود ندارد. مسیر ورودی وبهوک فقط منابع یکپارچهسازی (دیجیکالا، باسلام، ترب، زرینپال، ووکامرس، شاپیفای، سگمنت) را میپذیرد و هیچکدام پیامرسان نیستند. یعنی بات را خودتان اجرا میکنید: بات شما /start میگیرد، بکاند شما شناسه چت را به شناسه کاربر خودتان نگاشت میکند، و بکاند شما POST /v1/messenger/link را صدا میزند.
وقتی کمپین پوش میفرستد، دقیقا چه میشود
ترتیب مراحل خودش یک تصمیم است.
اول حاکمیت، بعد دستگاه. رضایت، سقف تعداد پیام، ساعت سکوت و تخصیص گروه کنترل همگی قبل از لمس رجیستری دستگاه اجرا میشوند. کسی که انصراف داده نباید پروندهاش خوانده شود، دستگاههایش فهرست شود یا محتوایش رندر شود.
رجیستری فقط برای کانالهایی خوانده میشود که یک نصب را آدرس میدهند، یعنی push و webpush. پیامک و ایمیل گیرندهشان را از روی پرونده پیدا میکنند، پس نداشتن دستگاه برای آنها بیمعنی است.
کوئری تحویل این است:
SELECT d.user_id, d.device_id, d.platform, d.has_gms,
d.app_version, d.locale, d.timezone, d.last_seen_at,
t.transport, t.token
FROM devices d
JOIN device_tokens t
ON t.tenant_id = d.tenant_id AND t.device_id = d.device_id AND t.retired_at IS NULL
WHERE d.tenant_id = $1
AND d.user_id = ANY($2)
AND d.revoked_at IS NULL
AND d.push_enabled
AND d.last_seen_at >= $3
ORDER BY d.user_id, d.last_seen_at DESC, d.device_id
پس یک دستگاه برای کمپین نامرئی است اگر: توکن زنده نداشته باشد، باطل شده باشد، push_enabled آن false باشد، بیش از ۱۸۰ روز دیده نشده باشد، بعد از پنجمین نصب تازه بیاید، یا اصلا user_id نداشته باشد.
نداشتن هیچ دستگاهی شکست نیست. نتیجه suppressed با دلیل not_reachable است. آن آدم وجود دارد و راضی است؛ ما فقط راهی برای رسیدن به او نداریم، و گفتن همین است که گزارش دسترسی را قابل عمل میکند نه مرموز.
یک نفر یک اعلان میگیرد. دستگاهها از تازهترین به قدیمیترین پیموده میشوند و در اولین دستگاهی که قبول کند متوقف میشود. فرستادن به همه نصبها یعنی کسی که دو بار گوشی عوض کرده همان پیام را سه بار میگیرد، که محتوایش هرچه باشد اسپم خوانده میشود.
برای هر دستگاه، روتر مسیر را انتخاب میکند. ترتیب اولویت:
| پلتفرم | ترتیب امتحان |
|---|---|
android | fcm، بعد bazaar، بعد myket، بعد huawei، بعد mqtt |
ios | apns، بعد mqtt |
web | webpush |
FCM اول است چون وقتی پلی سرویسز کار میکند سریعترین و ارزانترین مسیر است. بازار و مایکت بعدش، چون دقیقا همان دستگاههایی را میپوشانند که FCM نمیتواند. mqtt آخر است و امروز هیچ ارائهدهندهای ندارد، پس همیشه کنار میرود.
این فهرست با سه چیز فیلتر میشود: دستگاه واقعا برای آن مسیر توکن داشته باشد، ارائهدهندهای برایش پیکربندی شده باشد، و برای FCM، دستگاه has_gms: false گزارش نکرده باشد. صفر مسیر یعنی نتیجه rejected با متن push: no usable transport for this device.
جواب هر مسیر سرنوشت بعدی را تعیین میکند:
| نتیجه ارائهدهنده | چه میشود |
|---|---|
sent | برمیگردد، موفقیت روی مدار ثبت میشود |
invalid_token | مسیر بعدی امتحان میشود، و آن توکن بازنشسته میشود |
unavailable یا rate_limited | مسیر بعدی امتحان میشود، شکست روی مدار شمرده میشود |
rejected | همانجا برمیگردد. بدنه ردشده همهجا رد میشود و امتحان مسیر دیگر فقط سهمیه میسوزاند |
توکن مرده روی یک مسیر هیچ چیزی درباره مسیرهای دیگر نمیگوید، و همین رد شدن به مسیر بعدی است که مخاطبی را که FCM نمیتواند برساند بازمیگرداند.
توکن مرده بازنشسته میشود و آخرینش نصب را باطل میکند. وقتی ارائهدهنده بگوید توکن نامعتبر است، آن توکن retired_at میخورد. اگر آن دستگاه دیگر هیچ توکن زندهای نداشته باشد، خود دستگاه با revoked_reason = 'uninstall_detected' باطل میشود. این تنها سیگنال واقعبینانه حذف اپ است: اپ نمیتواند در حین حذف شدن unregister را صدا بزند، پس بدون این، پایگاه نصب هر مشتری فقط رشد میکرد. بازنشسته میشود نه حذف، تا اگر همان توکن با ثبت بعدی برگشت، نصب دوباره تشخیص داده شود.
مدار محافظ. ۱۰ شکست پشت سر هم یک مسیر را به مدت ۳۰ ثانیه باز میکند. اگر همه مسیرها بهعنوان در دسترس نبودن کنار رفتند، نتیجه unavailable با متن every transport was unavailable است.
هر پوش شناسههای انتساب را با خودش میبرد، و جای آنها در هر مسیر فرق میکند:
| مسیر | شناسهها کجا سوارند |
|---|---|
fcm | داخل message.data: sg_mid، sg_cid، sg_jid، sg_link، sg_t |
bazaar و myket و huawei | داخل data: sg_mid، sg_link، sg_t. بدون شناسه کمپین و سناریو. هواوی این نگاشت را بهصورت رشته JSON میگیرد نه شیء |
apns | سطح بالای بدنه، کنار aps: sg_mid، sg_link، sg_t |
webpush | داخل JSON رمزشده: mid، tkn، url |
شناسهها روی خود لینک هم سوار میشوند، پس رهگیری کلیک پوش به هیچ سرویس ریدایرکتی از طرف ما نیاز ندارد: آدم روی سایت خود مشتری فرود میآید که SDK آنجا از قبل حاضر است. پارامترهای UTM خود مشتری هم روی همان لینک میروند.
خواندن دستگاههای یک پرونده
این کارت پرونده در پنل است، نه یک اندپوینت که کلید API شما به آن برسد. مسیرش GET /v1/profiles/{user_id}/devices است و فقط روی کنترلپلین داشبورد ثبت شده؛ آن لیسنر عمدا از بیرون شبکه داخلی قابل دسترس نیست. همان مسیر روی https://api.segmentic.net به هندلر پیشفرض میافتد و 404 unknown_endpoint میگیرد.
پنل خودش این را با کوکی نشست شما و از راه پراکسی داخلیاش صدا میزند:
GET /api/proxy/v1/profiles/u_9137/devices
{
"devices": [
{
"platform": "android",
"transports": "bazaar,fcm",
"app_version": "3.4.0",
"model": "Xiaomi Redmi Note 12",
"timezone": "Asia/Tehran",
"push_enabled": true,
"revoked": false,
"last_seen": "2026-08-05T09:14:22Z"
}
]
}
مجوز لازم profile.read است. کسی که اجازه دارد ویژگیها و تاریخچه رویداد یک آدم را ببیند، از قبل نیمه حساستر ماجرا را میخواند.
| فیلد | نوع | توضیح |
|---|---|---|
platform | رشته | |
transports | رشته | با ویرگول جدا شده. یک نصب با دو توکن یک گوشی است نه دو تا. توکنهای بازنشسته هم در همین رشته میآیند، پس مسیری که اینجا میبینید لزوما زنده نیست |
app_version | رشته | وقتی خالی باشد نمیآید |
model | رشته | وقتی خالی باشد نمیآید |
timezone | رشته | وقتی خالی باشد نمیآید |
push_enabled | بولین | |
revoked | بولین | |
last_seen | رشته | RFC3339 به وقت UTC |
ترتیب last_seen_at DESC است و سقف 50 ردیف. نصبهای باطلشده عمدا در فهرست هستند: «شما اپ را سوم ماه حذف کردید» جواب سوال «چرا پوش نمیگیرم؟» است و پنهان کردن ردیف، سوال را بیجواب میگذارد.
خطاها: 400 با user_id is required، و 503 وقتی کوئری شکست بخورد.
درخواست حق فراموششدن اول device_tokens و بعد ردیف devices را حذف میکند، چون توکنها به device_id آویزاناند و در غیر این صورت تا ابد به آنها پوش میرفت. جزئیات در حریم خصوصی.
چیزهایی که وجود ندارند
اینجا صریح مینویسیم، چون یک شکاف صادقانه ارزانتر از یک جمله محتمل است که یک بعدازظهر شما را میگیرد.
- هیچ مسیر دستگاهی روی هاست مدیریت (
sk_seg_...)، حتی خواندن.GET /v1/profiles/{user_id}/devicesفقط روی کنترلپلین داشبورد ثبت شده و عمومی مسیریابی نمیشود، پس با کلید API نمیشود فهرست دستگاههای یک نفر را گرفت. - هیچ ابزار MCP مربوط به دستگاه یا پوش.
- ارائهدهنده MQTT. ثابتش هست، ثبت میشود، ارسال نمیشود.
- اندپوینت ثبت دستهای دستگاه. یک درخواست، یک دستگاه.
- هیچ شکل
GETیاDELETEاز این اندپوینتها. همهPOSTبا بدنه JSONاند. - ترتیب مسیر پیشفرض برای
windows،macosوlinux. ثبت میشوند، تحویل نمیگیرند. - هر جستجوی دستگاه بر اساس
anonymous_id. نصب ناشناس ذخیره میشود و هدف هیچ کمپینی نمیشود. - وبهوک پیام بات که شناسه چت بله، ایتا یا روبیکا را خودکار وصل کند.
- هر اعتبارسنجی روی شکل توکن FCM، بازار یا مایکت، فراتر از «خالی نباشد» و «از
4096بایت بلندتر نباشد». - هدر
apns-topicروی درخواستهای APNs، و با آنapns-push-type،apns-expiration،apns-priorityوapns-collapse-id. اپل برای احراز هویت مبتنی بر توکنapns-topicرا لازم دارد، پس این یک شکاف واقعی است نه ظرافت مستندات. عملا یعنیttl،collapse_keyوpriorityروی FCM اعمال میشوند و روی APNs نادیده گرفته میشوند. - اعتبارنامه APNs در سطح مشتری. کاتالوگ کانالها برای
pushفقطfcm،bazaar،myketوhuaweiدارد، پس پوش iOS برای همه مشتریهای یک نصب روی کلید APNs خود آن نصب میرود. - ارسال تاییدشده بازار یا مایکت با توکن واقعی فروشگاه. نگاشت توکن پذیرفته میشود ولی هرگز با توکن واقعی امتحان نشده، چون هر دو به حساب توسعهدهنده و اپ منتشرشده نیاز دارند.
- تست سطح توزیعکننده برای کانال وبپوش.