مرجع: نقاط ورود داده
هر مسیر روی میزبان ورود داده، با درخواست و پاسخ کامل و قابل کپی.
این صفحه هر مسیر روی میزبان ورود داده است، همان میزبانی که دستگاه کاربران شما با آن حرف میزند. کلید نوشتن عمومی میگیرد، نوشتن را میپذیرد، و تنها چیزی که تا به حال برمیگرداند یا عمومی است (فهرست کمپینهای روی سایت) یا با یک اعتبارنامه دوم محافظت میشود (صندوق درونبرنامهای). برای مخاطب و کمپین و گزارش و ارسال، API مدیریتی را ببینید.
میزبان
https://in.segmentic.net
روی ماشین خودتان، کالکتور به http://localhost:8080 گوش میدهد.
کل کار کالکتور این است: اعتبارسنجی کن، تکراریها را بردار، سریع تحویل بده، و هرگز به یک SDK نگو داده را دور بریزد چون مشکلی سمت ما هست. همین یک جمله بیشتر کدهای وضعیت پایین را توضیح میدهد: قطعی سمت ما یک 503 است که SDK دوباره امتحان میکند، نه یک 401 که آن را دائمی بفهمد.
https://in.segmentic.ir نام قدیمی است و عمدا به اینجا ریدایرکت نمیشود. SDKای که هنوز به آن اشاره میکند عمدا خراب میشود، نه اینکه ظاهرا کار کند.
احراز هویت با کلید نوشتن
کلید نوشتن شکل wk_seg_ است بهعلاوه ۴۳ کاراکتر base64url. میشود آن را در سه جا گذاشت و به همین ترتیب خوانده میشوند؛ اولین جایی که پیدا شود برنده است:
Authorization: Bearer wk_seg_...X-Segmentic-Key: wk_seg_...?write_key=wk_seg_...در کوئریاسترینگ
شکل عادی همان هدر است. پارامتر کوئری برای این هست که یک image beacon و یک فراخوان navigator.sendBeacon نمیتوانند هدر بگذارند، و SDK وب همین را برای GET /v1/onsite به کار میبرد تا آن درخواست یک GET ساده بیندامنهای بماند و preflight نخواهد.
از هدر Authorization فقط دقیقا پیشوند Bearer (با B بزرگ و یک فاصله) برداشته میشود. هر طرح دیگری رد نمیشود بلکه به جای بعدی میافتد، پس Authorization: Token wk_seg_... یعنی «اینجا توکن bearer نیست» و آن هدر کلا نادیده گرفته میشود.
کلید به یک حساب، یک اپ و محیط همان اپ ترجمه میشود. حساب و اپ روی هر رویداد از آن کلید مهر میخورند و هرگز از بدنه گرفته نمیشوند، پس payloadای که نام حساب دیگری را ببرد هیچ اثری ندارد. محیط روی هیچچیز مهر نمیخورد: روی همان اعتبارنامه حلشده میماند، هیچ ستونی در رویداد نگهش نمیدارد و هیچچیز روی این میزبان آن را نمیخواند. جستوجو بر اساس هش SHA-256 است و هیچجا پیشوند بررسی نمیشود، و به همین دلیل کلید API که به اینجا فرستاده شود صرفا یک کلید نوشتن ناشناخته است.
کلیدهای حلشده یک دقیقه کش میشوند (WRITE_KEY_CACHE)، و شکستها هم همینطور، چون اپی که با کلید غلط منتشر شده باشد وگرنه تا ابد دیتابیس را میکوبد.
ابطال آنی نیست و پنل هم چیز دیگری قول نمیدهد. تابعی هست که یک ورودی کش را پاک میکند و هیچجای محصول در حال اجرا صدایش نمیزند: پنل ابطال را در پستگرس مینویسد و کالکتور پروسه جداگانهای است که نقشه خودش را نگه داشته. پس کلیدی که در پنل باطل شود تا وقتی ورودی کشش منقضی شود، یعنی تا یک دقیقه بعد، همچنان پذیرفته میشود. برای کلید لو رفته روی همان یک دقیقه حساب کنید، نه روی لحظه فشردن دکمه.
وقتی احراز هویت شکست میخورد
| وضعیت | کد | بدنه | هدر |
|---|---|---|---|
| کلید در هیچکدام از سه جا نیست | 401 | {"status":"error","message":"missing write key"} | |
| کلید ناشناخته، باطلشده، یا حساب معلق | 401 | {"status":"error","message":"invalid write key"} | |
| خود جستوجو شکست خورد | 503 | {"status":"error","message":"cannot verify the write key right now; retry"} | Retry-After: 5 |
ناشناخته و باطلشده و معلق جوابهای بایتبهبایت یکسان میگیرند تا نشود از این نقطه فهمید چه کلیدهایی وجود دارند.
ردیف سوم قبلا 401 جواب میداد و آن بدترین جواب ممکن بود. SDK کد 401 را «این کلید هیچوقت کار نخواهد کرد» میفهمد، میایستد و رویدادهای بافرشده را دور میریزد؛ 503 را «بعدا امتحان کن» میفهمد و نگهشان میدارد. این را اندازه گرفتیم نه اینکه دربارهاش استدلال کنیم: با دیتابیسی که به صفر اسکیل شده بود، هشت رویداد از هشت رویداد 401 گرفتند. لاگ پیشنوشت (WAL) دقیقا برای این هست که یک خرابی زیرساخت هیچ رویدادی را از بین نبرد، و همان یک خط شکستش داد، چون درخواست اصلا به لاگ نمیرسید.
CORS
هر نقطهای که کلید نوشتن میخواهد، این هدرها را قبل از هر کاری مینویسد، حتی قبل از احراز هویت، تا مرورگر کد وضعیت واقعی را ببیند نه یک خطای CORS روی 401 یا 413:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization, X-Segmentic-Key
Access-Control-Max-Age: 86400
OPTIONS روی هر مسیری زیر /v1/ جواب 204 میدهد با همان هدرها و بدون بدنه.
Access-Control-Allow-Credentials هرگز ست نمیشود، و همین است که origin آزاد را بیخطر میکند. کلید نوشتن تنها اعتبارنامهای است که این میزبان میپذیرد و عمدا عمومی است، پس هیچ اعتبارنامه محیطیای نیست که مرورگر خودکار بچسباند و هیچ چیزی نیست که origin آزاد لو بدهد. کوکی به اینجا نفرستید؛ خوانده نمیشود.
GET /v1/status و نقاط ایمیلی زیر /e/ هدر CORS ندارند. آنها از داخل صفحه صدا زده نمیشوند.
بدنه درخواست
JSON، تا ۵ مگابایت (5242880 بایت) در هر درخواست. بیشتر از آن 413 است با {"status":"error","message":"request body too large"}.
Content-Type بررسی نمیشود. هندلرها بدنه را میخوانند و هرچه درخواست ادعا کرده باشد آن را JSON میخوانند، پس text/plain با بدنه JSON امروز کار میکند. با این حال application/json بفرستید.
هرچه JSON معتبر نباشد 400 است با {"status":"error","message":"malformed JSON"}. توجه کنید که این شامل بدنه سالمی هم میشود که یک زمان بدشکل دارد: timestamp و sent_at فقط به شکل RFC 3339 دیکود میشوند، پس "timestamp": 1786000000 کل درخواست را بهعنوان JSON خراب رد میکند، نه بهعنوان یک فیلد غلط.
فیلدهای بدنه رویداد
پنج نقطه تکرویدادی یک شکل بدنه مشترک دارند. هنگام تجزیه همه فیلدها اختیاریاند؛ اینکه واقعا چه چیزی لازم است به نقطه بستگی دارد.
| فیلد | نوع | لازم | توضیح |
|---|---|---|---|
event | رشته | فقط روی /v1/track | بعد از یکدستسازی حداکثر ۱۲۸ بایت. فارسی اشکالی ندارد، فاصلهها میمانند، حروف کوچک و بزرگ عوض نمیشوند و قاعده snake_case وجود ندارد |
user_id | رشته | یکی از user_id یا anonymous_id | حداکثر ۲۵۶ بایت |
anonymous_id | رشته | یکی از آن دو | حداکثر ۲۵۶ بایت، بدون قاعده قالب، لازم نیست UUID باشد |
previous_id | رشته | فقط روی /v1/alias | شناسهای که ادغام میشود. هیچ سقف طولی ندارد، برخلاف دو تای بالا |
message_id | رشته | نه، ولی بفرستید | حداکثر ۲۵۶ بایت. بدون آن، تلاش دوباره بهعنوان تکراری شناخته نمیشود |
timestamp | RFC 3339 | نه | پیشفرض همان لحظهای است که ما دریافت کردیم |
sent_at | RFC 3339 | نه | اصلاح اختلاف ساعت را ممکن میکند |
properties | شیء | نه | حداکثر ۲۵۶ کلید، هر کلید حداکثر ۱۲۸ بایت، مقدار رشتهای حداکثر ۸۱۹۲ بایت |
traits | شیء | نه | حداکثر ۲۵۶ کلید، با همان سقف مقدار |
context | شیء | نه | پایینتر |
type | رشته | اینجا نادیده گرفته میشود | نوع را مسیر تعیین میکند. فقط روی آیتمهای بسته لازم است |
type در بدنه یک درخواست تکرویدادی با مسیر بازنویسی میشود، پس POST /v1/track هر چیزی هم که بدنه بگوید فقط میتواند رویداد track بسازد. این خطای اعتبارسنجی نیست؛ فیلد فقط جایگزین میشود.
سقف ۲۵۶ بایت به سه شکل متفاوت اعمال میشود و همین تفاوت گاز میگیرد. user_id و anonymous_id وقتی بلند باشند رد میشوند، با id_too_long. context.session_id روی ۲۵۶ بایت بریده میشود، بیصدا، پس شناسه نشست بلند تبدیل به یک شناسه نشست دیگر میشود. previous_id هیچکدام نیست: کامل ذخیره میشود و تنها چیزی که محدودش میکند سقف ۵ مگابایتی بدنه است.
کلیدهای ویژگی و خصیصه یکدست میشوند: فضای اضافه بریده میشود، کاراکترهای کنترلی حذف میشوند، بعد هر دسته فاصله و هر . و - به یک _ تبدیل میشود و زیرخط ابتدا و انتها برداشته میشود. پس " spaced key " میشود spaced_key، و dotted.key میشود dotted_key و dashed-key میشود dashed_key. کلیدی که بعد از یکدستسازی خالی شود، کلا رد میشود.
مقدار ویژگیها هرجا معنا داشته باشد دو بار ذخیره میشود: همیشه به شکل متن، و وقتی عدد یا بولین باشد به شکل عدد هم. رشتهای که شبیه عدد است هرگز به عدد تبدیل نمیشود، چون تبدیل "01234" صفر ابتدایی یک کد پستی را دور میریزد و یک کد ملی بزرگ، رقمهای آخرش را به دقت عدد اعشاری میبازد. ویژگی با مقدار null بهجای ذخیرهشدن به شکل رشته خالی، کلا حذف میشود، تا فیلتر «مقدار ندارد» درست بماند.
شیء context
context دستگاه و اپ و صفحه را توصیف میکند. بخشی از آن روی رویداد ذخیره میشود و بخشی عمدا نمیشود، و این تفاوت مهم است چون روی چیزی که اصلا نگه داشته نشده نمیشود فیلتر گذاشت.
| فیلد | ذخیره میشود بهعنوان | سقف |
|---|---|---|
context.app.version | app_version | ۶۴ |
context.device.type | device_type | ۳۲ |
context.device.model | device_model | ۱۲۸ |
context.device.manufacturer | device_vendor | ۶۴ |
context.device.push_provider | push_provider | ۱۶ |
context.os.name و context.os.version | os_name (با حروف کوچک) و os_version | هرکدام ۳۲ |
context.network.carrier | carrier | ۶۴ |
context.page.url و .path و .referrer | page_url و page_path و page_referrer | هرکدام ۲۰۴۸ |
context.page.title | page_title | ۵۱۲ |
context.campaign.source و .medium و .name و .term و .content | utm_source و utm_medium و utm_campaign و utm_term و utm_content | هرکدام ۱۲۸ |
context.campaign.campaign_id و .journey_id | campaign_id و journey_id | عددی |
context.campaign.variant_id و .message_id و .token | variant_id و source_message_id و sg_t | ۶۴ و ۲۵۶ و ۱۲۸ |
context.locale و .timezone و .session_id | locale و timezone و session_id | ۳۲ و ۶۴ و ۲۵۶ |
context.location.country و .region و .city | country و region و city | هرکدام ۶۴ |
context.ip | ip، فقط وقتی از خود اتصال چیزی نگرفته باشیم | ۶۴ |
context.user_agent | هیچ. هدر User-Agent برنده است | |
context.screen.width و .height و .density | هیچ. پذیرفته و دور ریخته میشود | |
context.location.latitude و .longitude | هیچ. پذیرفته و دور ریخته میشود | |
context.device.id و .name و .push_token و .has_gms و .ad_tracking_enabled | هیچ. پذیرفته و دور ریخته میشود |
context.device.push_token عمدا دور ریخته میشود. فقط مسیر نگه داشته میشود و هرگز خود توکن: توکن پوش داخل جریان رویدادها یعنی کپیشدنش در انبار داده و هر خروجی و هر بکاپ، برای مقداری که رجیستری دستگاهها همین حالا مالکش است. توکن را با POST /v1/devices ثبت کنید.
کلاینت نمیتواند IP، user agent، نام مرورگر، پرچم ربات یا حساب را تعیین کند. اینها از خود اتصال و از کلید میآیند، چون کلاینت نباید بتواند موقعیت جغرافیایی یا دستگاه خودش را جعل کند. چیزهایی که SDK درباره دستگاه میفرستد بر آنچه از هدر User-Agent استخراج میشود مقدم است؛ هدر فقط جاهای خالی را پر میکند. ترافیک ربات پرچم میخورد و ذخیره میشود، هرگز حذف نمیشود، چون حذف بیصدای آن یک افت ترافیک را غیرقابلتوضیح میکند؛ هر گزارشی بهطور پیشفرض آن را کنار میگذارد.
در بیلد مستقرشده مکانیابی جغرافیایی از روی IP وجود ندارد. country و region و city فقط از context.location پر میشوند.
شکل پاسخ
هر پاسخ JSON روی این میزبان از این فیلدها ساخته میشود:
| فیلد | نوع | کی هست |
|---|---|---|
status | "ok" یا "error" | همیشه |
accepted | عدد | وقتی صفر نباشد |
duplicates | عدد | وقتی صفر نباشد |
rejected | عدد | وقتی صفر نباشد |
warnings | آرایهای از {code, field, note} | وقتی هشداری باشد |
errors | آرایهای از {index, reason} | وقتی آیتمی از بسته رد شده باشد |
message | رشته | روی خطا |
accepted وقتی صفر باشد حذف میشود، پس بستهای که کاملا رد شده باشد اصلا کلید accepted ندارد، و پاسخ خطا هم ندارد. آن را «اگر نبود یعنی صفر» بخوانید.
duplicates آن بخشی از accepted است که از قبل داشتیم. از accepted کم نمیشود، چون accepted به تنها سوالی جواب میدهد که یک SDK میپرسد، یعنی «میتوانم دیگر نفرستم»، و کلاینتی که هرچه پذیرفته نشده را دوباره بفرستد، یک تکراری را تا ابد میفرستد. پس accepted: 500 همراه duplicates: 493 یعنی هفت رویداد ذخیره شد و ۴۹۳ تا رویدادی بودند که از قبل داشتیم. نبودنش هم مثل بقیه یعنی صفر.
هشدار یعنی رویداد با یک اصلاح پذیرفته شد. کدهایی که ممکن است ببینید:
| کد | معنی |
|---|---|
generated_message_id | message_id نیامده بود، پس ما یکی ساختیم و تلاش دوباره برای این رویداد بهعنوان تکراری شناخته نمیشود |
timestamp_in_future | ساعت دستگاه بیش از یک ساعت جلو بود؛ روی زمان دریافت چسبانده شد |
timestamp_too_old | قدیمیتر از پنجره ورود داده حساب شما؛ روی لبه همان پنجره چسبانده شد |
too_many_properties | بیش از ۲۵۶ ویژگی؛ متن هشدار میگوید چند تا آمده و چند تا نگه داشته شده |
unserialisable_property | یک ویژگی قابل کدگذاری نبود؛ field نامش را میگوید |
too_many_traits | بیش از ۲۵۶ خصیصه |
invalid_phone | شماره موبایل ایرانی معتبر نیست؛ مقدار خام همانطور که آمده ذخیره شد |
invalid_national_id | کد ملی رقم کنترلش را رد کرد؛ خصیصه حذف شد |
به تفاوت دو تای آخر دقت کنید، که عمدی است: شماره بد نگه داشته میشود چون اغلب شماره واقعی با قالب غیرمنتظره است، و کد ملی بد حذف میشود چون کد ملیای که رقم کنترلش را رد کند کد ملی نیست.
POST /v1/track
ثبت میکند که یک آدم چه کرد. event لازم است؛ بدون آن جواب 400 missing_event_name است.
curl -X POST https://in.segmentic.net/v1/track \
-H "Authorization: Bearer wk_seg_..." \
-H "Content-Type: application/json" \
-d '{
"message_id": "m-1",
"event": "order_completed",
"user_id": "u_123",
"properties": { "revenue": 2500000, "currency": "IRR", "order_id": "8821", "city": "تهران" }
}'
{"status":"ok","accepted":1}
همان فراخوان بدون message_id این جواب را میدهد:
{
"status": "ok",
"accepted": 1,
"warnings": [
{
"code": "generated_message_id",
"field": "message_id",
"note": "no message_id sent; retries of this event cannot be de-duplicated"
}
]
}
درآمد از ویژگیها استخراج میشود، به این ترتیب: اولین مقدار غیرصفر از revenue و total و value؛ اگر نبود، price ضربدر quantity که در نبودش یک فرض میشود. currency پیشفرض IRR است و با حروف بزرگ ذخیره میشود، پس "irt" به شکل IRT میماند. هیچ تبدیل نرخی حدس زده نمیشود.
POST /v1/identify
خصیصهها را روی یک پرونده میگذارد. event نادیده گرفته میشود؛ نام رویداد ذخیرهشده همیشه identify است.
curl -X POST https://in.segmentic.net/v1/identify \
-H "Authorization: Bearer wk_seg_..." \
-H "Content-Type: application/json" \
-d '{
"message_id": "id-8821",
"user_id": "u_123",
"traits": {
"email": " Ali@Digikala.COM ",
"phone": "0912 345 6789",
"first_name": "علی",
"city": "کرج",
"gender": "مرد",
"lifetime_value": 48200000,
"is_subscriber": true,
"referral_code": "0912345"
}
}'
{"status":"ok","accepted":1}
آن payload به این تبدیل میشود:
| خصیصه | ذخیرهشده | چرا |
|---|---|---|
email | ali@digikala.com | فضای اضافه بریده و حروف کوچک میشود. هیچ اعتبارسنجی قالبی در کار نیست |
phone | +989123456789 بهعلاوه phone_operator: "mci" | هرچیزی جز E.164 برای یک آدم دو پرونده میسازد. اپراتور از چهار رقم اول درمیآید و 0912 یعنی mci (همراه اول) |
city | کرج | فارسی یکدست میشود، پس کیبورد عربی هم که كرج بفرستد همینجا مینشیند |
gender | male | تا میشود و نگاشت میشود. m و male و man و مرد و اقا و پسر همه male میشوند؛ مجموعه زنانه female میشود؛ هرچیز دیگر other |
lifetime_value | متن 48200000 و عدد 48200000 | خصیصه عددی دو بار نوشته میشود تا هم فیلتر «برابر» و هم «بزرگتر از» کار کند |
is_subscriber | متن true و عدد 1 | |
referral_code | فقط متن 0912345 | رشتهای که شبیه عدد است هرگز تبدیل نمیشود، پس صفر ابتدایی زنده میماند |
این دوبار نوشتن تزئینی نیست. بعد از این اضافه شد که یک حساب زنده با حدود ۱۱۵ هزار پرونده، برای هر خصیصه نقشه عددی خالی داشت، پس مخاطب «موجودی کلید ۱۰۰ یا بیشتر» هیچکس را برنمیگرداند و «کمتر از ۱۰» همه را برمیگرداند، از جمله کاربری که موجودیاش ۴۲۸ بود. نه خطایی، نه هشداری، مخاطبی که مثل یک جواب خوانده میشود.
national_id با رقم کنترل ایرانی اعتبارسنجی میشود و اگر رد شود حذف میشود، با هشدار invalid_national_id. مقدار معتبر با ارقام فارسی و عربیاش تبدیلشده به اسکی و فاصلههای دو سرش بریده ذخیره میشود، و در باقی چیزها همانطور که فرستادید. خود بررسی قبل از شمردن، خط تیره و فاصله را برمیدارد، هشت تا ده رقم را میپذیرد و مقدار کوتاه را فقط برای حساب خودش با صفر به ده رقم میرساند، پس 12345679 هشتکاراکتری ذخیره میشود، 001-234-5679 خط تیرههایش را نگه میدارد و 001 234 5679 فاصلههای داخلیاش را نگه میدارد. اگر سگمنتها و جوینهای شما ده رقم انتظار دارند، همان شکل دهرقمی را بفرستید.
POST /v1/page و POST /v1/screen
بدنه یکی است. event اینجا اختیاری است: بدون آن نام رویداد ذخیرهشده روی /v1/page میشود page_viewed و روی /v1/screen میشود screen_viewed.
curl -X POST https://in.segmentic.net/v1/page \
-H "Authorization: Bearer wk_seg_..." \
-H "Content-Type: application/json" \
-d '{
"message_id": "p-4471",
"anonymous_id": "a_9f21c0",
"event": "product",
"properties": { "sku": "DKP-118820" },
"context": {
"page": {
"url": "https://shop.example.ir/p/118820?utm_source=sms",
"path": "/p/118820",
"title": "گوشی موبایل",
"referrer": "https://www.google.com/"
},
"session_id": "s_20260807_01",
"locale": "fa-IR"
}
}'
{"status":"ok","accepted":1}
POST /v1/alias
تاریخچه ناشناس را به آدم واردشده میچسباند. previous_id لازم است؛ بدون آن جواب 400 missing_previous_id است. نام رویداد ذخیرهشده همیشه alias است.
curl -X POST https://in.segmentic.net/v1/alias \
-H "Authorization: Bearer wk_seg_..." \
-H "Content-Type: application/json" \
-d '{"message_id":"al-1","user_id":"u_123","previous_id":"a_9f21c0"}'
{"status":"ok","accepted":1}
SDKها این را خودکار روی اولین identify بعد از گشتوگذار ناشناس میفرستند، پیش از خود identify. اگر کلاینت خودتان را مینویسید، همین را کپی کنید: بدون alias کل تاریخچه پیش از ورود کاربر یتیم میشود و هر قیفی که از مرز ورود عبور کند عدد اشتباه گزارش میدهد.
POST /v1/batch
تا ۵۰۰ رویداد در یک درخواست. هر آیتم type خودش را دارد و اینجا این فیلد نادیده گرفته نمیشود بلکه تعیینکننده است: باید یکی از track، identify، alias، page، screen باشد.
curl -X POST https://in.segmentic.net/v1/batch \
-H "Authorization: Bearer wk_seg_..." \
-H "Content-Type: application/json" \
-d '{
"sent_at": "2026-08-07T09:12:41Z",
"context": { "locale": "fa-IR", "app": { "version": "5.2.1" } },
"batch": [
{"type":"track","message_id":"b1","event":"product_viewed","user_id":"u1"},
{"type":"track","message_id":"b2","event":"checkout_started","user_id":"u2"},
{"type":"identify","message_id":"b3","user_id":"u3","traits":{"phone":"09123456789"}}
]
}'
{"status":"ok","accepted":3}
context و sent_at سطح بالا پیشفرضاند: در هر آیتمی که مال خودش را نداشته باشد کپی میشوند، و مقدار خود آیتم هرگز بازنویسی نمیشود.
یک آیتم بد، آیتمهای خوب را از بین نمیبرد. پاسخ اندیس را میگوید تا منطق تلاش دوباره شما بتواند آیتم را بدون تطبیق محتوا پیدا کند:
{
"status": "ok",
"accepted": 2,
"rejected": 1,
"errors": [ { "index": 1, "reason": "missing_identity" } ]
}
دو شکست کل درخواست را رد میکنند، هر دو با 400: آرایه batch خالی (batch_empty) و بیش از ۵۰۰ آیتم (batch_too_large: 501 items, limit 500). رد شدن به دلیل سهمیه هم کل بسته را میگیرد و هرگز بخشی از آن را نمیپذیرد، چون پذیرش نصفه SDK را از تشخیص اینکه کدام آیتمها را باید دوباره بفرستد ناتوان میکند.
بستهای با پنجاه رویداد یک رفتوبرگشت برای حذف تکراری و یک رفتوبرگشت برای انتشار هزینه دارد، نه پنجاه تا از هرکدام. آرایه warnings در پاسخ حدود پنجاه ورودی سقف دارد تا بستهای با پانصد رویداد کمی غلط، یک مگابایت نصیحت جواب ندهد؛ همه هشدارها همچنان در متریکهای حساب شمرده میشوند.
دیباگر زنده رویداد در پنل، رویدادهایی را که از /v1/batch میآیند نمیبیند. ضبط فقط روی مسیر تکرویدادی و مسیر وبهوک انجام میشود. هر SDK موبایلی بسته میفرستد و SDK وب هم همینطور، پس اگر به دیباگر نگاه میکنید و چیزی نمیبینید در حالی که accepted بالا میرود، دلیلش همین است.
message_id و حذف تکراری
message_id همان چیزی است که تلاش دوباره را بیخطر میکند. SDKها روی شبکه موبایل بیثبات با شدت دوباره میفرستند، پس بدون آن شمارش خرید بیصدا دو برابر میشود.
- دامنهاش حساب شماست. دو حساب میتوانند یک
message_idمشترک داشته باشند بدون اینکه به هم بخورند. - پنجره ۴۸ ساعت است. باید با فاصله از طولانیترین تلاش دوباره SDK بیشتر باشد: کلاینت اندرویدی که یک روز رویدادها را آفلاین بافر کرده و بعد فرستاده، باید هنوز شناخته شود.
- تکراری
200جواب میگیرد باaccepted: 1وduplicates: 1، دقیقا مثل تحویل اول، چون SDKای که خطا بگیرد تا ابد دوباره میفرستد. داخل بسته هم تکراریها به همان دلیل درacceptedشمرده میشوند و کنارش درduplicatesگزارش میشوند. - شناسه پیام قبل از انتشار رویداد رزرو میشود و اگر انتشار شکست بخورد همان رزرو پس داده میشود، پس رویدادی که
503گرفته، وقتی SDK دوباره میفرستدش، تکراری حساب نمیشود. - تکراری صورتحساب نمیشود. SDKای که دوباره میفرستد برای ما یک جستوجوی کش هزینه دارد، نه یک خط فاکتور که شما سرش بحث کنید.
- اگر انبار حذف تکراری در دسترس نباشد، رویداد به هر حال پذیرفته و منتشر میشود. پذیرفتن یک تکراری احتمالی قطعا بهتر از گمکردن رویداد است: تکراری پاییندست قابل تعمیر است و داده نبوده نیست.
مکانیزم یک «بنویس اگر نبود» با زمان انقضاست، نه فیلتر بلوم، پس مثبت کاذب ندارد.
زمان و اختلاف ساعت
timestamp یعنی آن اتفاق کی افتاد، روی دستگاه. sent_at یعنی دستگاه کی درخواست را فرستاد. دومی همان چیزی است که اجازه میدهد اولی را اصلاح کنیم.
- نبودن
timestampیعنی زمانی که ما دریافت کردیم. بدون هشدار. timestampبیش از یک ساعت جلوتر از ساعت ما، روی زمان دریافت چسبانده میشود با هشدارtimestamp_in_future. زمان جلوتر از الان فقط از ساعت غلط دستگاه میآید، و رد نکردنش رویداد را در بازههایی میگذارد که گزارشها آنها را نهایی کردهاند.timestampقدیمیتر از پنجره ورود داده حساب شما، روی لبه همان پنجره چسبانده میشود با هشدارtimestamp_too_old. پنجره پیشفرض ۳۰ روز است؛ حسابی که رویدادها را بیشتر نگه میدارد پنجره بلندتری میگیرد. این عدد بهازای حساب است نه یک ثابت سراسری.- در غیر این صورت، اگر
sent_atآمده باشد و با ساعت ما بیش از یک دقیقه فرق داشته باشد، کل اختلاف بهtimestampاضافه میشود. مقدار اصلاحشده فقط وقتی استفاده میشود که هنوز داخل پنجره بیفتد. برای اصلاح هیچ هشداری داده نمیشود.
یک مثال کامل: ساعت دستگاه دو ساعت عقب است. میگوید رویداد ساعت ۰۸:۰۰ رخ داده و ساعت ۱۰:۰۰ فرستاده شده. ما ساعت ۱۲:۰۰ دریافت میکنیم. اختلاف دو ساعت است، پس زمان ذخیرهشده ۱۰:۰۰ است نه ۰۸:۰۰.
ورود داده زنده همیشه میچسباند و هرگز زمان خارج از پنجره را رد نمیکند. یعنی مهاجرت تاریخچه از این میزبان، بیصدا هرچه قدیمیتر از پنجره است را روی یک لحظه روی هم میریزد، 200 جواب میدهد و درست به نظر میرسد تا ماهها بعد که یک قیف بیمعنا شود. این سر یک حساب واقعی که دو سال تاریخچه منتقل میکرد اتفاق افتاده است. تاریخچه را از /v1/track یا /v1/batch نریزید.
کدهای وضعیت روی نقاط رویداد
| وضعیت | کد | بدنه |
|---|---|---|
| پذیرفته شد | 200 | {"status":"ok","accepted":1} و در صورت وجود warnings |
| پذیرفته شد و تکراری بود | 200 | یکسان |
| کلید نوشتن نیست | 401 | {"status":"error","message":"missing write key"} |
| کلید بد، باطل یا معلق | 401 | {"status":"error","message":"invalid write key"} |
| جستوجوی کلید شکست خورد، قطعی ماست | 503 | {"status":"error","message":"cannot verify the write key right now; retry"} |
| بدنه بیشتر از ۵ مگابایت | 413 | {"status":"error","message":"request body too large"} |
| بدنه JSON نیست | 400 | {"status":"error","message":"malformed JSON"} |
| حساب از سهمیه گذشته | 402 | {"status":"error","message":"<یک جمله فارسی>"} |
| اعتبارسنجی رویداد تکی شکست خورد | 400 | {"status":"error","message":"<متن کامل دلیل>"} |
| بسته خالی یا بیش از ۵۰۰ | 400 | {"status":"error","message":"batch_empty"} یا "batch_too_large: 501 items, limit 500" |
| بعضی آیتمهای بسته بد بودند | 200 | {"status":"ok","accepted":N,"rejected":M,"errors":[...]} |
| هم گذرگاه و هم بافر دیسک شکست خوردند | 503 | {"status":"error","message":"temporarily unavailable, please retry"} |
دلایل رد کدهای پایداریاند، چون پنل آنها را به فارسی نگاشت میکند و مشتریها رویشان هشدار میگذارند: unknown_type، missing_identity، missing_event_name، event_name_too_long، event_name_invalid_chars، id_too_long، missing_previous_id، batch_too_large، batch_empty. دو تای آنها مقدار خود شما را داخل پیام میآورند، مثل unknown_type: "trak"، پس روی پیشوند تطبیق بدهید نه روی تساوی.
ردیف آخر تنها موردی است که SDK باید به دلیل داده دوباره تلاش کند. قطعی گذرگاه بهتنهایی آن را نمیسازد: کالکتور روی یک لاگ پیشنوشت محلی میافتد، پس پایینبودن صف اصلا برای شما دیده نمیشود. باید هر دو شکست بخورند. توجه کنید که این 503 هدر Retry-After ندارد؛ فقط 503 مربوط به جستوجوی کلید دارد.
پاسخ 402 صرفنظر از Accept-Language شما فارسی است. کالکتور مذاکره زبان ندارد: هیچچیز درخواست را با یک زبان برچسب نمیزند، پس آن جمله هر بار به فارسی برمیگردد. روی کد وضعیت شرط بگذارید، نه روی متن.
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": "d_5f2a91c4",
"user_id": "u_123",
"platform": "android",
"tokens": { "fcm": "cZ1x...:APA91b..." },
"push_enabled": true,
"has_gms": true,
"app_version": "5.2.1",
"manufacturer": "Samsung",
"model": "SM-A546E",
"os_name": "android",
"os_version": "14",
"locale": "fa-IR",
"timezone": "Asia/Tehran",
"sdk_name": "segmentic-android",
"sdk_version": "1.4.0"
}'
{"status":"ok"}
| فیلد | نوع | لازم | توضیح |
|---|---|---|---|
device_id | رشته | بله | حداکثر ۲۵۶ بایت |
user_id | رشته | یکی از آن دو | |
anonymous_id | رشته | یکی از آن دو | |
platform | رشته | بله | android، ios، web، windows، macos، linux و نامهای جایگزینی مثل iphone، ipad، osx، darwin، win، browser. مقدار server رد میشود |
tokens | شیء | یک توکن قابل استفاده | نام حامل به توکن |
push_provider و push_token | رشته | نه | شکل قدیمی تکمسیره. اگر هر دو بیایند tokens برنده است |
push_enabled | بولین | نه | نبودنش یعنی روشن، تا SDK قدیمی کاربران خودش را ساکت نکند |
has_gms | بولین | نه | نبودنش یعنی «نگفت»، که با false یکی نیست |
app_version، manufacturer، model، os_name، os_version، locale، timezone، sdk_name، sdk_version | رشته | نه | هرکدام حداکثر ۲۵۶ بایت |
کدام حامل به کدام پلتفرم میرسد:
| پلتفرم | حاملها |
|---|---|
android | fcm، bazaar، myket، mqtt |
ios | apns، mqtt |
web، windows، macos، linux | webpush |
توکنی که روی حامل اشتباه بیاید بهجای ذخیرهشدن، بیرون نگه داشته میشود و هشدار میگیرد، چون بدون این بررسی حالت خرابی «خطا» نیست: کمپینی است که گزارش میدهد صد درصد فرستاده شد و هیچچیز تحویل نمیدهد.
آن جدول میگوید ثبت چه چیزی را میپذیرد، نه اینکه به چه چیزی میشود تحویل داد، و دو سطرش امروز اصلا چیزی تحویل نمیدهند. هیچ ارائهدهنده mqttای پیادهسازی نشده: نام حامل یک ثابت است و در ترتیب اولویت روتر هم نشسته، و پشتش هیچ کدی چیزی نمیفرستد، پس دستگاه اندروید یا iOS که فقط توکن mqtt دارد بیمشکل ثبت میشود و هرگز در دسترس نیست. windows و macos و linux هم در روتر پوش مسیری ندارند: جدول اولویت بهازای پلتفرم روتر فقط android و ios و web را دارد، پس ثبت دسکتاپ ذخیره و شمرده میشود و هرگز چیزی برایش نمیرود. fcm یا apns یا webpush روی web ثبت کنید، و ثبت دسکتاپ یا mqtt را دفترداری بخوانید نه دسترسپذیری.
توکنهای APNs در راه ورود تعمیر میشوند. APIهای قدیمی iOS توکن را به شکل <a1b2 c3d4> رشته میکنند، و فرستادن همان عینا برای همیشه از طرف اپل رد میشود، پس کروشهها و فاصلهها حذف و مقدار با حروف کوچک ذخیره میشود.
شکستها 400 جواب میگیرند با دلیل، و برخلاف معمول همراه با هشدارها:
{
"status": "error",
"message": "device: registration carries no usable token",
"warnings": [
{ "code": "transport_not_supported", "message": "...", "field": "apns" }
]
}
بدون آنها، توسعهدهنده SDKای که از بیلد اندروید توکن APNs میفرستد فقط «توکن قابل استفادهای نیست» را میبیند و هیچ سرنخی ندارد. کدهای هشدار اینجا empty_token و token_too_long (بیش از ۴۰۹۶) و transport_not_supported و fcm_without_gms هستند. دلایل رد، عینا، اینها هستند: device: device_id is required و device: platform must be one of android, ios, web, windows, macos, linux و device: user_id or anonymous_id is required و device: registration carries no usable token. ثبتی که push_enabled: false دارد و توکن ندارد پذیرفته میشود، چون آن یک تغییر وضعیت واقعی است.
شکست ذخیرهسازی 503 است با {"status":"error","message":"temporarily unavailable, please retry"}. برخلاف رویداد، ثبت ناموفق هیچ بافری پشتش ندارد، پس SDK باید دوباره تلاش کند.
POST /v1/devices/unregister
curl -X POST https://in.segmentic.net/v1/devices/unregister \
-H "Authorization: Bearer wk_seg_..." \
-H "Content-Type: application/json" \
-d '{"device_id":"d_5f2a91c4","user_id":"u_123","revoked":false}'
{"status":"ok"}
device_id لازم است. بدون آن جواب 400 {"status":"error","message":"device_id is required"} است، و بدنهای که JSON معتبر نباشد هم دقیقا همین جواب را میگیرد نه malformed JSON را.
revoked: true یعنی اپ حذف نصب شده و آن نصب رفته است. revoked: false، که پیشفرض است، یعنی خروج از حساب: کاربر جدا میشود و توکن میماند. این را روی خروج از حساب صدا بزنید. روی یک گوشی مشترک، ماندن حساب قبلی یعنی نفر بعدی بهروزرسانی سفارش یک نفر دیگر را میگیرد.
وبپوش
فقط وقتی سرو میشود که وبپوش پیکربندی شده باشد. دو مسیر.
POST /v1/webpush/subscribe همان چیزی را میگیرد که مرورگر به شما داده، چه تودرتو چه تخت:
curl -X POST https://in.segmentic.net/v1/webpush/subscribe \
-H "Authorization: Bearer wk_seg_..." \
-H "Content-Type: application/json" \
-d '{
"user_id": "u_123",
"subscription": {
"endpoint": "https://fcm.googleapis.com/fcm/send/dK9...",
"p256dh": "BJ7s...",
"auth": "k1Qb..."
}
}'
{"status":"ok"}
شکل تخت هم کار میکند:
{"user_id":"u_123","endpoint":"https://...","p256dh":"BJ7s...","auth":"k1Qb..."}
شکل تودرتو برای این هست که صفحه بتواند هرچه مرورگر داده را بدون باز کردنش بفرستد، و مهمتر، بدون کدگذاری دوباره کلیدها. رشته base64 که یک کمککننده خوشنیت آن را دیکود و دوباره کد کرده باشد، کلاسیکترین راهی است که یک اشتراک بیصدا از رمزگشایی میافتد.
هر چهار فیلد user_id و endpoint و p256dh و auth لازماند. نبودن هرکدام 400 {"status":"error","message":"user_id and a complete subscription are required"} است، چون endpoint بدون کلید بیفایده است: payload را نمیشود رمز کرد.
POST /v1/webpush/unsubscribe فقط 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/dK9..."}'
{"status":"ok"}
خود endpoint راز آن اشتراک است. داشتنش همین حالا برای فرستادن به آن مرورگر کافی است، پس خواستن چیز بیشتری برای اینکه کسی بتواند دریافت را قطع کند، محافظت از جهت اشتباه است. endpoint خالی 400 {"status":"error","message":"endpoint is required"} است.
شکست ذخیرهسازی روی هر دو مسیر 503 است نه یک 200 بلعیدهشده، چون کاربر همین حالا اجازهای داده که صفحه نمیتواند دو بار بخواهدش.
اشتراک وبپوش بهتنهایی هیچکس را در دسترس نمیکند. پیش از رسیدن به فرستنده وبپوش، هر ارسال روی کانال webpush ردیفهای دستگاه همان کاربر را میخواند و اگر ردیفی نباشد پیام را با دلیل not_reachable سرکوب میکند. این بررسی چه رجیستری دستگاه پیکربندی شده باشد چه نشده باشد اجرا میشود، پس روی نصبی که انبار دستگاه ندارد هر وبپوشی سرکوب میشود، و کمپین همان سرکوب را گزارش میدهد نه خطا را. اگر فقط پوش مرورگر را یکپارچه میکنید، همان کاربر را علاوه بر اشتراک با POST /v1/devices هم ثبت کنید (platform: "web")، و قبل از اینکه کمپینی روی آن بسازید با یک ارسال آزمایشی مطمئن شوید.
پیامرسانها
فقط وقتی سرو میشود که پیامرسانها پیکربندی شده باشند. چت بله، ایتا یا روبیکا را به یک پرونده وصل میکند.
curl -X POST https://in.segmentic.net/v1/messenger/link \
-H "Authorization: Bearer wk_seg_..." \
-H "Content-Type: application/json" \
-d '{
"user_id": "u_123",
"platform": "bale",
"chat_id": "44120099",
"username": "ali_gh",
"source": "bot_start"
}'
{"status":"ok"}
platform باید دقیقا bale یا eitaa یا rubika باشد. هرچیز دیگر، از جمله telegram، جواب 400 {"status":"error","message":"user_id, chat_id and a known platform are required"} میگیرد. ستون پشت آن در دیتابیس یک قید بررسی دارد، پس مقدار ناشناخته وگرنه پایینتر با خطایی شکست میخورد که هیچکس نمیتواند رویش کاری بکند.
user_id و chat_id و یک platform معتبر لازماند. username و source اختیاریاند و همانطور که آمدهاند رد میشوند. مقداری که رضایت واقعی را حمل میکند bot_start است، یعنی خود آدم بات را شروع کرده؛ هرچیز دیگری ارزش این را دارد که بعدا بشود پیدایش کرد.
POST /v1/messenger/unlink فقط 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_123","platform":"bale"}'
{"status":"ok"}
صندوق درونبرنامهای
فقط وقتی سرو میشود که صندوق پیکربندی شده باشد. این تنها مسیر روی این میزبان است که داده شخصی یک آدم را میخواند، و کلید نوشتن عمومی نمیتواند چیزی باشد که از آن محافظت میکند.
هر درخواست یک اعتبارنامه دوم دارد، user_hash، که بکاند خودتان هنگام ورود کاربر حساب میکند:
user_hash = lowercase_hex( HMAC-SHA256( identity_secret, user_id ) )
printf '%s' "u_123" \
| openssl dgst -sha256 -hmac "$SEGMENTIC_IDENTITY_SECRET" -r \
| cut -d' ' -f1
راز هویت هرگز به مرورگر یا اپ نمیرسد. هیچ صفحهای در پنل آن را صادر نمیکند و هیچ مسیر APIای برنمیگرداندش: آن را یک اپراتور سگمنتیک میسازد، پس گرفتنش یعنی از ما خواستن. چرخاندنش هر هشی را که قبلا دادهاید باطل میکند، که تا وقتی دوباره منتشر نکنید کل اپ شما را از صندوقش بیرون میاندازد.
curl -X POST https://in.segmentic.net/v1/inbox \
-H "Authorization: Bearer wk_seg_..." \
-H "Content-Type: application/json" \
-d '{
"user_id": "u_123",
"user_hash": "9f1c0b7d3e5a...",
"limit": 20
}'
{
"status": "ok",
"messages": [
{
"message_id": "c104.u_123",
"title": "سفارش شما ارسال شد",
"body": "بسته شما تحویل پست شد.",
"image": "https://cdn.example.ir/parcel.png",
"deeplink": "myapp://orders/8821",
"surface": "inbox",
"token": "1.7.k2.ce.mfz1t8.9c4a...",
"created_at": "2026-08-07T09:00:00Z",
"expires_at": "2026-08-21T09:00:00Z",
"seen": false
}
]
}
messages همیشه آرایه است و هرگز null نیست، تا SDKای که بدون بررسی nil رویش حلقه میزند یک حلقه خالی بگیرد نه یک کرش.
token امضای انتساب همان پیام است. آن را در context.campaign.token روی رویداد message_opened که به /v1/track میفرستید برگردانید، تا بشود ثابت کرد آن باز شدن مال پیامی است که واقعا ما فرستادهایم.
POST است نه GET و دو دلیل دارد: اثبات هویت جایش در بدنه است نه در کوئریاسترینگی که هر پراکسی و هر تاریخچه مرورگر و هر لاگ دسترسی در مسیر یک کپی از آن نگه میدارد، و خود خواندن اثر جانبی دارد، چون ردیفها با علامت «تحویل شد» برمیگردند.
limit بدون تغییر به انبار داده میرود. صفر یعنی سقف خود انبار اعمال میشود، و آن سقف اینجا منتشر نشده است.
هر شکست هویتی همان 403 است:
{"status":"error","message":"user identity is not verified"}
هش غلط، هش نیامده و حسابی که اصلا راز هویت ندارد از هم قابل تشخیص نیستند، چون تشخیصدادنشان این نقطه را به ابزاری برای فهمیدن اینکه چه شناسههای کاربری وجود دارند تبدیل میکند. حالت «تاییدنشده» وجود ندارد.
POST /v1/inbox/ack همان اعتبارنامه را میگیرد بهعلاوه دو آرایه:
curl -X POST https://in.segmentic.net/v1/inbox/ack \
-H "Authorization: Bearer wk_seg_..." \
-H "Content-Type: application/json" \
-d '{
"user_id": "u_123",
"user_hash": "9f1c0b7d3e5a...",
"seen": ["c104.u_123"],
"dismissed": ["c99.u_123"]
}'
{"status":"ok"}
این ack هیچ تعاملی ثبت نمیکند. باز شدن درونبرنامهای از مسیر عادی رویداد گزارش میشود، با همان توکنی که صندوق به شما داده، تا دقیقا با همان بررسی امضایی تایید شود که هر کانال دیگری با آن تایید میشود. مسیر دوم و مورد اعتماد برای همان سیگنال، یک چیز دیگر برای خراب شدن بود، و این یکی به حرف فراخوانی اعتماد میکرد که چیزی جز یک کلید نوشتن عمومی ندارد.
پیامهای روی سایت
فقط وقتی سرو میشود که این قابلیت پیکربندی شده باشد. سه مسیر: چه چیزی نشان بده، و چه اتفاقی افتاد.
GET /v1/onsite تنها درخواستی در محصول است که روی مسیر بحرانی رندر سایت یک نفر دیگر میدود، و هر تصمیمی دربارهاش از همین درمیآید. هیچ هویت کاربری ندارد، پس یک پاسخ به همه بازدیدکنندهها خدمت میکند و CDN میتواند کشش کند. بهجای تصمیم، قاعده هدفگیری برمیگرداند، پس مرورگر بدون رفتوبرگشت همانجا تطبیق میدهد.
curl "https://in.segmentic.net/v1/onsite?write_key=wk_seg_..."
HTTP/1.1 200 OK
Cache-Control: public, max-age=60
Content-Type: application/json; charset=utf-8
{
"campaigns": [
{
"id": 12,
"name": "بنر تخفیف نوروز",
"kind": "banner",
"status": "live",
"content": { },
"targeting": { },
"max_impressions": 3,
"cooldown_hours": 24,
"dismissible": true,
"starts_at": "2026-03-15T00:00:00Z",
"ends_at": "2026-03-25T00:00:00Z",
"impressions": 41822,
"clicks": 1104,
"dismissals": 380
}
],
"cache_seconds": 60
}
kind یکی از banner و modal و slidein و survey است. content و targeting در نمونه بالا جمع شدهاند؛ شکلشان در پیامهای روی سایت هست. شصت ثانیه آنقدر بلند هست که بیشتر بازدیدهای یک فروشگاه شلوغ اصلا به ما نرسد، و آنقدر کوتاه هست که مکثکردن یک کمپین وقتی هنوز همان آدمی که دکمه را زده نگاه میکند اثر بگذارد. بهخاطر همین پنجره، مرورگر تاریخ شروع و پایان را دوباره محلی چک میکند، تا کمپینی که پایانش وسط کش رد شود بدون منتظرماندن متوقف شود.
قاعدههای هدفگیری در این پاسخ عمومیاند. هرکسی میتواند آنها را در تب شبکه بخواند، و به همین دلیل واژگان قاعده چیزی ندارد که شما ناراحت شوید یک رقیب ببیندش. چیز محرمانهای داخل قاعده هدفگیری نگذارید.
شکست ذخیرهسازی اینجا 200 با فهرست خالی جواب میدهد، هرگز 5xx. این داخل بارگذاری صفحه شما میدود: خرابی ما باید به «امروز بنری نیست» تنزل کند، نه به یک خطای کنسول روی سایت شما.
POST /v1/onsite/event ثبت میکند چه اتفاقی افتاد:
curl -X POST https://in.segmentic.net/v1/onsite/event \
-H "Authorization: Bearer wk_seg_..." \
-H "Content-Type: application/json" \
-d '{
"campaign_id": 12,
"anonymous_id": "a_9f21c0",
"action": "click",
"page_url": "https://shop.example.ir/p/118820"
}'
{"status":"ok"}
campaign_id و یکی از user_id یا anonymous_id لازماند؛ بدونشان جواب 400 {"status":"error","message":"campaign_id and a visitor id are required"} است. action یکی از impression (رشته خالی هم همین معنی را دارد) و click و dismiss و convert است و به حروف کوچک و بزرگ حساس نیست؛ هرچیز دیگر 400 {"status":"error","message":"unknown action"} است.
شکست ذخیرهسازی باز هم 200 جواب میدهد. گمشدن یک شمارش نمایش، یک عدد روی داشبورد هزینه دارد؛ برگرداندن خطا به اسکریپتی که داخل صفحه شما میدود، یک خطای کنسول روی هر بازدید صفحه هزینه دارد.
POST /v1/onsite/response جواب یک نظرسنجی را ثبت میکند و score و answers را هم میگیرد:
curl -X POST https://in.segmentic.net/v1/onsite/response \
-H "Authorization: Bearer wk_seg_..." \
-H "Content-Type: application/json" \
-d '{
"campaign_id": 31,
"user_id": "u_123",
"score": 9,
"answers": { "why": "ارسال سریع بود" },
"page_url": "https://shop.example.ir/thanks"
}'
{"status":"ok"}
کمپین بهجای اینکه به آن اعتماد شود، خوانده و بررسی میشود: اینکه این یک نظرسنجی NPS هست یا نه تعیین میکند که امتیاز اصلا معنایی دارد یا نه، و مرورگر مرجع این موضوع نیست. شناسهای که پیدا نشود 400 {"status":"error","message":"unknown campaign"} است. کمپینی که نظرسنجی نیست 400 {"status":"error","message":"onsite: this campaign is not a survey"} است. وقتی کمپین NPS تنظیم شده باشد امتیاز باید آمده باشد و بین ۰ تا ۱۰ باشد وگرنه onsite: an NPS score must be between 0 and 10؛ وقتی NPS نباشد امتیاز به -1 مجبور میشود، یعنی «بدون امتیاز». نیامدن این فیلد رد میشود، نه اینکه صفر خوانده شود. قبلا صفر خوانده میشد و صفر یک نمره منتقد معتبر است، پس بدنهای که اصلا امتیاز نداشت بهعنوان بدترین جواب مقیاس ذخیره میشد و امتیازی را که همان نفر قبلا داده بود جایگزین میکرد. پاسخ متنی بلندتر از ۲۰۰۰ کاراکتر بهجای رد شدن بریده میشود، چون کسی که سه پاراگراف درباره تحویل سفارشش نوشته حرفی زده که ارزش نگهداشتن دارد. ثبت یک پاسخ یک convert هم ثبت میکند، تا کسی که نظرش را گفته هفته بعد همان سوال را نگیرد.
شکست ذخیرهسازی اینجا 503 است، برخلاف دو مسیر دیگر روی سایت: یک پاسخ، شمارنده نیست.
GET /v1/status
بدون احراز هویت، بدون خواندن دیتابیس، بدون محدودیت نرخ.
curl -i https://in.segmentic.net/v1/status
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Cache-Control: no-store, no-cache, must-revalidate
X-Server-Time: 2026-08-07T09:12:41Z
{"status":"ok","service":"collector","version":"1.42.0"}
version مهر بیلد کالکتوری است که دارد میدود، تا «آیا استقرار رفته بالا» از بیرون قابل جوابدادن باشد. /readyz و نقطه متریکها روی یک listener مدیریتی جداگانهاند و جزو این سطح نیستند.
مسیرهایی که فقط با پیکربندی وجود دارند
قابلیتی که جایی برای نوشتن ندارد اصلا سرو نمیشود، بهجای اینکه دادهای را بپذیرد که بیصدا دور میریزد. 404 همان لحظه به توسعهدهنده SDK میگوید که یک قابلیت پیکربندی نشده؛ 200ای که بیصدا اشتراک را دور انداخته باشد هفتهها بعد پیدا میشود، توسط کمپینی که به هیچکس نرسیده.
| مسیرها | کی سرو میشود |
|---|---|
/v1/track، /v1/identify، /v1/page، /v1/screen، /v1/alias، /v1/batch، /v1/status | همیشه |
/v1/devices، /v1/devices/unregister | وقتی انبار دستگاه پیکربندی شده باشد |
/v1/webpush/subscribe، /v1/webpush/unsubscribe | وقتی وبپوش پیکربندی شده باشد |
/v1/messenger/link، /v1/messenger/unlink | وقتی پیامرسانها پیکربندی شده باشند |
/v1/inbox، /v1/inbox/ack | وقتی صندوق پیکربندی شده باشد |
/v1/onsite، /v1/onsite/event، /v1/onsite/response | وقتی پیامهای روی سایت پیکربندی شده باشند |
مسیر ثبتنشده زیر /v1/ جواب 405 Method Not Allowed میدهد نه 404. الگوی preflight مربوط به CORS هر مسیری زیر آن پیشوند را برای OPTIONS برداشته، پس روتر مسیر را میشناسد و متد را نه. 405 اینجا را «این قابلیت روی این استقرار روشن نیست» بخوانید و با همان چشمی نگاهش کنید که به 404 نگاه میکنید. بیرون از /v1/، مسیر ثبتنشده یک 404 ساده است.
بقیه مسیرهای این میزبان
اینها روی همین میزباناند و جزو سطح SDK نیستند. هرکدام جای خودشان توضیح داده شدهاند.
| مسیر | چیست |
|---|---|
GET /e/o | پیکسل باز شدن ایمیل. همیشه یک GIF شفاف جواب میدهد، حتی برای توکن جعلی، چون تصویر شکسته وسط یک ایمیل تبلیغاتی آشکارترین نقصی است که گیرنده میبیند |
GET /e/u و POST /e/u | لغو اشتراک یککلیکی. آن GET عمدا لغو اشتراک نمیکند؛ رضایت و لغو اشتراک را ببینید |
GET /e/p و POST /e/p | مرکز ترجیحات گیرنده |
POST /v1/hooks/{source}/{token} | وبهوک پلتفرمها از دیجیکالا، باسلام، ترب، زرینپال، ووکامرس، شاپیفای و سگمنت؛ وبهوکها را ببینید |
POST /v1/bounce/{local} | ورودی برگشت ایمیل، که با نشانی بازگشت خودش آدرسدهی میشود |
GET /sdk/* | باندل SDK مرورگر، که از همین origin سرو میشود تا یک ورودی در سیاست امنیت محتوای شما هم اسکریپت و هم درخواستهایش را پوشش بدهد |
GET /s/* | اینجا یک 404 قطعی. لینکهای کوتاه روی دامنه کوتاه خودشان هستند، چون دامنه کوتاهتر یعنی کاراکتر کمتر در هر پیامک |
هیچکدام از اینها کلید نوشتن نمیگیرند. نقاط ایمیلی بهجایش یک توکن امضاشده میگیرند، که قویتر است: کلید نوشتن عمدا عمومی است و امضا نیست.
چیزهایی که این میزبان ندارد
- هیچ نوع محدودیت نرخی نیست. نه در ثانیه، نه بهازای کلید، نه بهازای IP، نه روی لبه و نه داخل برنامه. تنها کنترل حجم سهمیه ماهانه است که
402جواب میدهد. 503مربوط به شکست انتشار هدرRetry-Afterندارد. فقط503جستوجوی کلید دارد. از عقبنشینی خودتان استفاده کنید.Content-Typeاعمال نمیشود. بدنه JSON با هر برچسبی پذیرفته میشود.- مذاکره زبان نیست.
Accept-Languageنادیده گرفته میشود؛ تنها پیام آدمخوان روی این میزبان، یعنی رد شدن به دلیل سهمیه، همیشه فارسی است. - مکانیابی جغرافیایی از روی IP نیست. مکان فقط از
context.locationمیآید. - نسخه
GETوPUTوDELETEنقاط رویداد وجود ندارد.GET /v1/trackجواب405میدهد. - روی مسیر بسته ضبط دیباگ نیست، پس دیباگر زنده رویداد در پنل نسبت به ترافیک بستهای کور است.
- کوکی سمت سرور و شناسه ناشناس ساختهشده توسط سرور وجود ندارد. ماندگارکردن
anonymous_idکاملا کار SDK است، و اگر کلاینت خودتان را مینویسید، کار شماست. - هیچ راهی برای خواندن دوباره یک رویداد نیست. هیچچیز روی این میزبان آنچه فرستادهاید را برنمیگرداند. آن را در پنل یا از API مدیریتی کوئری کنید.