دادههای شخصی: خروجی گرفتن و حذف
کاری که وقتی کاربری میخواهد دادهاش را ببیند یا پاک کند باید بکنید، و کاری که سگمنتیک خودش میکند.
درخواست حذف را نمیشود از API فرستاد. همهی مسیرهای /v1/privacy/* و /v1/settings/retention روی گوشدهندهی کنترلی پنلاند، که عمداً از بیرون شبکهی داخلی قابل آدرسدهی نیست. روی api.segmentic.net هیچ نقطهی پایانی حریم خصوصی وجود ندارد. اگر میخواهید درخواستهای کاربرانتان خودکار به سگمنتیک برسد، امروز چنین راهی نیست و باید یک آدم آنها را در پنل ثبت کند.
در رابطهی حقوقی، شما کنترلکنندهی داده هستید و سگمنتیک پردازشگر. این صفحه دقیقاً میگوید پلتفرم چه میکند و چه نمیکند، تا سیاست حریم خصوصی خودتان چیزی را قول ندهد که کد انجامش نمیدهد.
وقتی کسی میپرسد چه دادهای از من دارید
هیچ نقطهی پایانی «همهی دادهی این آدم را بده» وجود ندارد. برای جمعکردن جواب یک درخواست دسترسی، پنج مسیر جدا هست و همه دسترسی profile.read میخواهند:
| مسیر | چه میدهد | سقف |
|---|---|---|
GET /v1/profiles/{user_id} | پرونده: ویژگیها و ستونهای محاسبهشده | یک پرونده |
GET /v1/profiles/{user_id}/timeline | رویدادهای خود کاربر | ۱۰۰ رویداد آخر |
GET /v1/profiles/{user_id}/messages | چه فرستادیم و چه شد، شامل ارسالهایی که عمداً انجام نشد | ۵۰ سطر آخر |
GET /v1/profiles/{user_id}/devices | دستگاههای نصبشده و وضعیت پوش | همه |
GET /v1/profiles/{user_id}/preferences | تنظیم کانال و موضوع | همه |
سقف ۱۰۰ رویداد یعنی تایملاین، تاریخچهی کامل نیست. اگر آن آدم هزار رویداد دارد، این مسیر جواب کاملی به یک درخواست دسترسی نمیدهد و راه دیگری هم برای گرفتن بقیهاش از API نیست.
خروجی kind برابر profiles کل حساب را میدهد، نه یک نفر را. فیلتری برای یک شناسه ندارد. یعنی ساختن پاسخ یک درخواست دسترسی، کار دستی است: این پنج صفحه را باز کنید و بخوانید.
سطرهای «ارسال نشد» در تاریخچهی پیام، نیمهی باارزشتر آن فهرستاند. «چیزی نفرستادیم چون شما در تاریخ فلان از این موضوع لغو اشتراک کردید» جواب خود شکایت است، و فقط به این دلیل وجود دارد که مسیر ارسال، امتناعها را به همان دقت ارسالها ثبت میکند.
فایل کردن درخواست حذف
تقسیم دسترسی اینجا از هر صفحهی دیگری تیزتر است، چون اینها تنها دو نقطهی پایانی پلتفرماند که عمداً دادهی مشتری را نابود میکنند.
| مسیر | دسترسی |
|---|---|
GET /v1/privacy/erasures | profile.read |
GET /v1/privacy/erasures/{id} | profile.read |
POST /v1/privacy/erasures | profile.write |
POST /v1/privacy/erasures/{id}/reject | settings.write |
GET /v1/settings/retention | settings.read |
PUT /v1/settings/retention | settings.write |
فایل کردن یک حذف، نام یک آدم را میبرد، پس همان جفت دسترسی را میگیرد که هر مسیر دیگر رسیدن به یک فرد میگیرد. نگهداری از جنس دیگری است: یک عدد که تعیین میکند چه مقدار از تاریخچهی خود حساب زنده میماند، پس settings.write است و فقط دست مالک و ادمین. بازاریابی که میتواند به دو میلیون نفر پیام بدهد، همچنان نمیتواند تصمیم بگیرد رویدادهای پارسال دیگر وجود نداشته باشند.
رد کردن یک درخواست هم settings.write است، چون امتناع از اجرای یک درخواست قانونی، تصمیمی دربارهی حساب است نه دربارهی آن شخص.
شکل درخواست
درخواستهای این بخش را با curl نمیشود زد. آنچه پایین میآید، همان درخواستی است که پنل روی کنترلپلین میفرستد، و اینجا آمده تا بدانید دقیقاً چه چیزی ثبت میشود و پاسخ چه شکلی است.
POST /v1/privacy/erasures HTTP/1.1
Content-Type: application/json
{"kind": "phone", "identifier": "+98 912 345 6789"}
kind یکی از user_id یا email یا phone است و اگر ننویسید user_id فرض میشود. شناسه قبل از هششدن نرمال میشود، نه اینکه همانطور که آمده باور شود: دو املای یک شماره که جدا هش شوند، دو تعهد برای یک آدم با دو مهلت میسازند و دومی هرگز با جستوجوی اولی پیدا نمیشود.
user_idفقط فضای اضافهاش گرفته میشود. کلید خود شماست و ممکن است بزرگی و کوچکی حروف در آن معنا داشته باشد.emailکوچک میشود و باید@داشته باشد که نه اول باشد و نه آخر.phoneفقط رقمهایش نگه داشته میشود، با ارقام فارسی و عربی هم کار میکند. چهار شکل09123456789و+989123456789و00989123456789و9123456789به یک شکل میرسند. حذفی که سه شکل از این چهار تا را جا میانداخت، موفقیت گزارش میکرد و آن آدم کاملاً در دسترس میماند.
پاسخ ۲۰۲ است:
{
"id": 41,
"subject_hash": "Q1p4bF9y...",
"subject_ref": "09123456789",
"subject_kind": "phone",
"status": "pending",
"requested_by": "member:12",
"requested_at": "2026-08-07T09:20:00Z",
"due_at": "2026-09-06T09:20:00Z",
"report": {
"profiles": 0, "events": 0, "messages": 0, "devices": 0, "consent": 0,
"journey_state": 0, "identities": 0, "suppressions_kept": 0,
"segment_membership": 0
},
"attempts": 0,
"status_label": "در صف",
"kind_label": "شمارهٔ همراه",
"overdue": false,
"removed_total": 0,
"days_remaining": 30,
"requested_label": "۱۶ مرداد ۱۴۰۵"
}
خطاها:
| حالت | کد |
|---|---|
شناسه خوانا نیست، یا kind ناشناخته است | ۴۰۰ |
| برای همین آدم یک درخواست باز وجود دارد | ۴۰۹ |
| کلید هش روی این نصب تنظیم نشده | ۵۰۳ |
آن ۵۰۳ عمدی است. هش، خود سند ماجراست؛ بدون کلید جایی برای گذاشتن سند دائمی نیست، پس درخواست رد میشود بهجای اینکه چیزی ثبت شود که بعداً نمیشود دربارهاش گزارش داد. خواندن صف در همین حالت هم کار میکند: نصبی که نمیتواند درخواست تازه بگیرد، باید بتواند آنچه در دفتر است را نشان بدهد.
صف و مهلت
due_at هنگام درج، سی روز بعد از requested_at نوشته میشود و بعدها تغییر سیاست، مهلتهای در جریان را جابهجا نمیکند. overdue سمت سرور حساب میشود، نه در مرورگر: «آیا آن ماه گذشته است» نباید به ساعت لپتاپی که به صفحه نگاه میکند وابسته باشد.
GET /v1/privacy/erasures?limit=50&offset=0 HTTP/1.1
{"erasures": [], "total": 0, "overdue": 0}
overdue جدا از فهرست میآید تا صفحه بتواند با آن شروع کند. فهرستی که بر اساس تاریخ مرتب شده، همان یک درخواستی را که از مهلتش گذشته، وسط بقیه دفن میکند. limit بین ۱ تا ۲۰۰ است و خارج از آن به ۵۰ برمیگردد.
پنج وضعیت وجود دارد: pending، running، completed، failed، rejected. جاروکش هر یک دقیقه سراغ صف میآید و تا ۵۰ کار پشتسرهم برمیدارد، تا میز پشتیبانی که یکشنبه چهل درخواست ثبت کرده، چهل دقیقه منتظر چهلمی نماند. یک درخواست ناموفق تا ۵ بار دوباره تلاش میشود.
اگر هیچکس با آن شناسه پیدا نشود، درخواست کامل ثبت میشود، نه ناموفق: چیزی از او نیست که پاک شود، و درخواستی که تا ابد روی شناسهای که هرگز اینجا نبوده تلاش کند، از مهلت قانونیاش رد میشود و شبیه نقض تعهد به نظر میرسد.
جاروکش عمداً باینری جداگانهای است. هر کاری که میکند یا یک mutation روی ClickHouse است یا یک حذف بزرگ روی Postgres، و هر دو دقیقاً همان ظرفیت دیسک و ادغامی را میخواهند که مسیر ورود داده لازم دارد. ضمناً همان باینریای است که یک اپراتور نگران میتواند متوقفش کند، بدون اینکه ارسال پیام متوقف شود.
رد کردن یک درخواست
POST /v1/privacy/erasures/41/reject HTTP/1.1
Content-Type: application/json
{"reason": "این حساب تحت نگهداشت قانونی پروندهی فلان است"}
reason باید دستکم ۱۰ نویسه باشد، وگرنه ۴۰۰. امتناعی که دلیلش نوشته نشده، اولین چیزی است که یک ممیز دربارهاش میپرسد، و «اپراتور چیزی ننوشت» جوابی نیست که یک کنترلکننده بتواند بدهد.
فقط درخواستی که pending یا failed است رد میشود؛ باقی حالتها ۴۰۹. رد کردن، subject_ref را هم پاک میکند: درخواست ردشده هم نباید به انبار شناسه تبدیل شود. خود ردشدن حذف نمیشود، ثبت میشود، چون امتناعی که ردی از خودش نمیگذارد، از درخواستی که کسی نخوانده قابل تشخیص نیست.
{"id": 41, "status": "rejected"}
چه چیزی پاک میشود
ترتیب، خود قرارداد است:
- اول آدرس ایمیل در فهرست مسدود ثبت میشود، تا اگر کار وسط راه شکست، آن آدم دستکم محافظت شده باشد نه فقط نصفهحذف.
- بعد Postgres، در یک تراکنش، تا وضعیت عملیاتی یا کاملاً رفته باشد یا کاملاً مانده.
- آخر ClickHouse، چون mutation ناهمگام است و در تراکنش هیچکس نمیگنجد.
از Postgres این جدولها روی user_id پاک میشوند: devices، inapp_messages، journey_instances، journey_timers، campaign_timers، message_log، messenger_identities، topic_consent، user_consent، webpush_subscriptions. توکنهای دستگاه اول از همه میروند، چون به device_id وصلاند نه به کاربر و بعد از رفتن دستگاهها چیزی برای پیوستنشان نمیماند و تا ابد به آنها پوش فرستاده میشود.
این فهرست دستی نوشته شده و از کاتالوگ پایگاه داده کشف نمیشود، چون information_schema جدولهای audit_log و memberships و sessions را هم برمیگرداند که user_id آنها عددی است و یکی از کارمندان خود مشتری را نام میبرد. حذفی که روی نام ستون تطبیق میداد، حساب کارمندی را پاک میکرد که تصادفاً شناسهاش با مشتری حذفشده یکی بود.
از ClickHouse: profiles، events، segment_members، daily_user_stats، engagement، message_touch. daily_user_stats فقط شمارش دارد، ولی روی user_id کلید خورده، پس هر سطرش این جمله است که «این آدم در این روز وجود داشت»، و همان چیزی است که حذف باید بردارد. engagement و message_touch دفترهای انتساباند: «این آدم آن پیام را باز کرد» به همان اندازهی خود خرید، حقیقتی دربارهی اوست.
بعد از آن، تاریخچهی پیش از ورود: رویدادهایی که user_id خالی دارند و فقط از راه anonymous_idهای داخل identity_map قابل پیدا شدناند. این کار قبل از پاککردن identity_map انجام میشود و ترتیبش تمام ماجراست. نسخهی اول این کد نقشه را اول پاک میکرد، پس زیرپرسمان به هیچ سطری نمیخورد، حذف چیزی برنمیداشت و موفقیت گزارش میکرد: تاریخچهی گشتوگذار آن آدم پیش از ورود به حساب، که بخش بزرگی از معنی «مرا فراموش کن» است، از هر حذفی روی این پلتفرم بیصدا جان سالم به در میبرد. یک تست یکپارچگی گرفتش.
آخرین قدم خود identity_map است، چون اندیس همهی چیزهای بالاست.
هر mutation با mutations_sync = 2 اجرا میشود، یعنی تا وقتی همهی رپلیکاها تمام نکنند برنمیگردد. کندتر است و تنها تنظیمی است که زیر آن، «انجام شد» یعنی انجام شد؛ حالت پیشفرض بهمحض صفشدن mutation برمیگردد و گزارش را در حالی موفق میکرد که سطرها هنوز سر جایشاناند.
گزارش، شمارش است نه سطر. گزارشی که فهرست میکرد چه چیزی را پاک کرده، خودش نسخهای از دادهی پاکشده بود، داخل همان جدولی که تمام هدفش ثبت این است که آن داده دیگر نیست.
هیچ نصب منتشرشدهای بدون انبار داده اجرا نمیشود: هم API و هم janitor موقع بالا آمدن یک اتصال ClickHouse باز میکنند و اگر جواب ندهد بالا نمیآیند. خود لایهی ذخیره شاخهی «بدون انبار» را دارد، و این شاخه بیصداست نه اینکه خودش را توضیح بدهد: قدم ClickHouse را رد میکند و profiles و events و identities و segment_membership را روی صفر میگذارد، یعنی همان گزارشی که یک انبار جستوجوشده و خالی هم میداد. گزارش ۹ شمارنده است و فیلدی برای این تفاوت ندارد، پس اینکه آن قدم اجرا شده یا نه، حقیقتی دربارهی استقرار است نه چیزی که بشود از گزارش خواند.
چه چیزی میماند، و چرا
حذف، user_consent و topic_consent را پاک میکند. یعنی لغو اشتراک پوش و پیامک و موضوعهای آن آدم، همراه خودش میرود. فقط ایمیل با یک سطر مسدودسازی دائمی محافظت میشود. اگر فردا همان شماره را دوباره وارد کنید، آن آدم دوباره با پیامک قابل دسترسی است، مگر اینکه قبلاً با پیام «لغو» جواب داده باشد، که آن یکی روی خود شماره ثبت میشود و دستنخورده میماند.
بعد از یک حذف کامل، اینها روی سیستم باقی میمانند:
- سطر خود درخواست.
subject_hashوsubject_kindوrequested_byو تاریخها و شمارشهای گزارش میمانند؛subject_ref، یعنی شناسهی خواندنی، لحظهی تمامشدن کار خالی میشود. نگهداشتن شناسه بعد از پایان کار یعنی جدولی که حذف را ثبت میکند، خودش سابقهی آدمی است که خواسته حذف شود. - آدرس ایمیل آن آدم، برای همیشه. سطر مسدودسازی با دلیل
erasureنوشته میشود و تاریخ انقضایشNULLاست، هرچه قبلاً بوده. این را صریح میگوییم چون در سند حقوقی مهم است: سگمنتیک بعد از حذف، همچنان آدرس ایمیل را نگه میدارد. بدون آن، سطر برگشتی همان آدرس روزی منقضی میشد و ارسال به کسی که خواسته فراموش شود، دوباره مجاز میشد. - شمارهای که قبلاً «لغو» فرستاده. جدول لغو پیامک روی خود شماره کلید خورده، نه روی شناسهی کاربر، و حذف به آن دست نمیزند. عمدی است: پاسخ از یک گوشی میآید و آدم پشتش ممکن است به هیچ پروندهای نخورد، یا به چند پرونده بخورد، یا به پروندهای بخورد که فردا پاک و دوباره وارد میشود. حذف روی این سطر یعنی همان سه حالت، مسدودسازی را از بین میبرد.
- این جدولها که حذف اصلاً سراغشان نمیرود: امتیاز درگیری و امتیاز ریزش، هم در Postgres و هم در نسخهی آینهشان در ClickHouse؛ پاسخهای نظرسنجی درونسایتی؛ صف تحویل رله، که یک نسخهی JSON از خود رویداد را نگه میدارد. هیچکدام سطر کاربر حذفشده را پاک نمیکنند و در شمارشهای گزارش هم نمیآیند.
- امتیاز RFM و عضویت شرطهای ورود سناریو، هر بار از صفر بازنویسی میشوند، پس سطر آن آدم در اولین محاسبهی بعدی خودبهخود میرود.
- شمارندهی روزانهی کاربران فعال تهران، که حالت تجمیعی است نه سطر آدمها. بازنویسی نمیشود، پس شخص حذفشده همچنان داخل شمارش متمایز روزهای گذشته حساب میشود.
- فایلهای خروجی که قبلاً ساخته شدهاند. حذف به آنها دست نمیزند. طبق ساعت هفتروزهی خودشان پاک میشوند. اگر کسی قبلاً خروجی گرفته و روی لپتاپش دارد، آن نسخه از دسترس ما بیرون است.
خط لاگ برنامه، شناسهی درخواست و نقش کسی که ثبتش کرده را مینویسد و هرگز خود شناسه را نمینویسد. آن خط، همان رد ممیزی است و ردی که نام آن آدم را ببرد، نسخهای است از دقیقاً همان چیزی که قرار است پاک شود.
هش، HMAC روی شناسهی حساب و شناسهی نرمالشده است، با کلیدی که از کلید مخفی نصب گرفته میشود. کلیددار بودنش تمام نکته است: یک آدرس ایمیل آنقدر آنتروپی ندارد که از SHA-256 خام جان سالم به در ببرد، و جدولی از هشهای ساده را با یک واژهنامه در چند دقیقه میشود برگرداند، یعنی جدولی از آدرسهای ایمیل با یک کلاه. شناسهی حساب داخل خود MAC است، پس یک آدرس در دو حساب، دو توکن متفاوت میسازد؛ بدون آن، یک جدول هش لو رفته به یک مشتری اجازه میداد بفهمد آدم مشخصی مشتری آن یکی هم هست یا نه.
نگهداری
حذف را یک نفر میخواهد؛ نگهداری را همه، چه بخواهند چه نخواهند. پلتفرمی که فقط اولی را پیاده کند، از سوال ممیزی رد میشود و همچنان چهار سال تاریخچهی گشتوگذار یک نفر را نگه داشته است.
GET /v1/settings/retention HTTP/1.1
{
"policy": {
"events_days": 365,
"messages_days": 180,
"bounces_days": 90,
"inactive_profile_days": 0,
"last_swept_at": "2026-08-06T02:14:00Z",
"updated_at": "2026-05-02T11:00:00Z",
"updated_by": "member:1"
},
"min_days": 30,
"max_days": 3650
}
PUT /v1/settings/retention HTTP/1.1
Content-Type: application/json
{"policy": {"events_days": 365, "messages_days": 180,
"bounces_days": 90, "inactive_profile_days": 0}}
| فیلد | چه چیزی را میبرد |
|---|---|
events_days | رویدادهای خام رفتاری |
messages_days | دفتر ارسال: به چه کسی، کی، و چرا نفرستادیم |
bounces_days | گزارشهای برگشتی و شکایت |
inactive_profile_days | پروندههایی که از آن زمان دیده نشدهاند |
صفر یعنی برای همیشه، روی هر چهار فیلد. پیشفرض درست برای پلتفرمی است که نباید دادهی مشتری را بهخاطر جاماندن یک فیلد پاک کند: حذف همیشه انتخابی است، و سیاست خالی، سیاستی نیست که تصادفاً چیزی پاک نکند، سیاستی است که عمداً چیزی پاک نمیکند.
هر مقدار غیرصفر باید دستکم ۳۰ روز و حداکثر ۳۶۵۰ روز باشد. مقدار خارج از این بازه رد میشود، نه اینکه به مرز نزدیک شود. بریدن بیصدا، همان چیزی است که باعث میشود کسی باور کند حدی گذاشته که نگذاشته، و اینجا آن باور دربارهی این است که دادهی مشتریانش چقدر زنده میماند.
کف ۳۰ روز بهخاطر سختی فنی نیست. زیر حدود یک ماه، حساب نمیتواند به «ماه گذشته چه شد» جواب بدهد، هر گزارش ماهبهماه خالی است، و آن قسمتی که واقعاً تیکت پشتیبانی میسازد: پنجرهی انتساب یک کمپین از رویدادهایی که با آنها سنجیده میشود عمر بیشتری دارد، پس کمپین برای خریدهای واقعی، صفر تبدیل گزارش میکند. سقف ۳۶۵۰ روز هم لبهی بیرونی هر تعهد نگهداری سوابق تجاری در ایران است؛ بعد از آن، عدد دیگر سیاست نیست، فیلدی است که فراموش شده.
جاروکش هر ۱۵ دقیقه دنبال حسابی میگردد که باید جارو شود، هر حساب حداکثر یک بار در ۲۴ ساعت، و هر پاس حداکثر ۵ حساب. جاروی ناموفق last_swept_at را نمینویسد تا پاس بعدی دوباره امتحان کند؛ علامتزدن جاروی شکستخورده بهعنوان انجامشده، همان راهی است که نگهداری یک حساب بیصدا از کار میافتد.
روی رویدادها، هر ماه که کاملاً منقضی شده با DROP PARTITION میرود، که فقط فایلها را جدا میکند و تقریباً رایگان است. جدول رویدادها بر اساس (شناسهی حساب، ماه) پارتیشن شده و شناسهی حساب عمداً اول است تا همین ممکن باشد. فقط ماهی که مرز روی آن افتاده mutation واقعی میخورد، و آن یکی دقیق است، تا سیاست همان تعداد روزی را که میگوید معنا بدهد، نه «جایی بین N و N بهعلاوهی سی».
فهرست مسدودسازی ایمیل عمداً در این جدول نیست و هرگز جارو نمیشود. فهرست مسدودی که منقضی شود، ارسال به آدرسی را دوباره مجاز میکند که برگشت خورده، شکایت کرده، یا صاحبش خواسته فراموش شود. این همان خرابیای است که بیشتر از هر چیز دیگری اعتبار دامنهی ارسال را از بین میبرد و اپراتور کمتر از هر چیز دیگری متوجهش میشود.
سقفهای ثابت پلتفرم
مستقل از سیاست شما، چند عدد در خود اسکیمای انبار داده نشستهاند:
| چه چیزی | چقدر | مستقل از سیاست حساب |
|---|---|---|
جدول events | ۴۰۰ روز | بله |
دفتر engagement | ۴۰۰ روز | بله |
ادعاهای ردشده در engagement (امضای نامعتبر و مانند آن) | ۳۰ روز | بله |
| فایل خروجی | ۷ روز | بله |
| پنجرهی ضبط دیباگر رویداد | ۳۰ دقیقه | بله |
پس سیاست نگهداری ۳۶۵۰ روز روی رویدادها، ۳۶۵۰ روز رویداد به شما نمیدهد. جدول خودش بعد از ۴۰۰ روز سطرها را میبرد.
در جهت دیگر، این جدولها هیچ TTL ندارند و جاروی نگهداری هم به آنها دست نمیزند: daily_user_stats، segment_members، identity_map، message_touch. یعنی سیاست نودروزه روی رویدادها، همچنان برای هر کاربر و هر روز یک سطر در daily_user_stats باقی میگذارد، برای همیشه. آن سطر خود رویداد نیست، شمارش روزانهی همان آدم است، ولی همچنان میگوید آن آدم آن روز فعال بوده.
پیکسل باز شدن و ردیابی کلیک
کلیکهای ایمیل از هیچ ریدایرکتی رد نمیشوند. لینکهای داخل پیام دو پارامتر میگیرند، sg_mid و sg_t، و گیرنده مستقیم روی سایت خود شما مینشیند؛ SDKای که همانجاست ورودش را گزارش میکند. دلیلش این است که دروازهی ایمیل شرکتی هر لینک هر پیام را قبل از رسیدن به گیرنده باز میکند تا بدافزار پیدا کند، پس یک ریدایرکت شمارنده، بیشتر یک فهرست سازمانی را «کلیککرده» گزارش میکرد.
نتیجهاش برای سیاست حریم خصوصی شما: سگمنتیک کلیک را فقط وقتی میبیند که سایت یا اپ خودتان رویدادش را بفرستد. اگر صفحهی مقصد SDK ندارد، هیچ دادهی کلیکی وجود ندارد. آن رویداد را سیستم شما ثبت میکند، با هر چیزی که خود سیستم شما ضمیمه کند.
باز شدن ایمیل فرق دارد، چون صفحهی فرودی ندارد. یک پیکسل لازم دارد و آن پیکسل روی میزبان ورودی است:
GET /e/o?sg_mid=c104.u_9137&sg_t=8mBv2h7oQ1w HTTP/1.1
Host: in.segmentic.net
پاسخ همیشه یک GIF شفاف یکدریک، ۴۳ بایت، است. حتی با توکن جعلی، حتی وقتی پیامی با آن شناسه نداریم، حتی وقتی پایگاه دادهی خودمان پایین است. عکس شکسته وسط یک ایمیل بازاریابی، آشکارترین عیبی است که گیرنده میبیند و هیچکدام از آن خرابیها تقصیر او نیست.
وقتی امضا معتبر باشد و گیرنده پیدا شود، یک رویداد message_opened ثبت میشود که اینها را همراه دارد:
- شناسهی کاربر گیرنده و شناسهی پیام.
- نشانی IP کسی که تصویر را گرفته، و کشور و استان و شهر حاصل از همان IP.
- رشتهی User-Agent همان درخواست، که برای تشخیص مرورگر و سیستمعامل و ربات پارس میشود.
- زمان، از ساعت خودمان و هرگز از هدر. فاصلهی بین ارسال و باز شدن یکی از معدود عددهای صادق ایمیل است و مقداری که ادعاکننده کنترلش میکند، اندازهگیری نیست.
پیکسل با هدر Cache-Control: no-store و Pragma: no-cache میآید، چون گوگل و بقیهی ارائهدهندههای بزرگ تصاویر بیرونی را پراکسی و کش میکنند و بدون این هدرها، پراکسی یک بار میگیرد و تا ابد نسخهی خودش را نشان میدهد، پس هر بار باز کردن بعد از اولی نامرئی است.
ردیابی باز شدن فقط جایی روشن میشود که یک توکن امضا وجود داشته باشد. بدون توکن، پیکسل اصلاً داخل پیام گذاشته نمیشود: هر کسی میتواند یک نشانی را باز کند، پس شمارش یک ضربهی بدون امضا، همان عددی را باد میکند که این کانال با آن سنجیده میشود.
لغو اشتراک هم روی همین میزبان است، و نکتهاش این است که لغو اشتراک با GET، لغو نمیکند. GET /e/u صفحهای با یک دکمه نشان میدهد که POST میکند. اسکنرهای امنیتی بیشتر بانکها و خردهفروشهای بزرگ ایران هر لینک هر پیام ورودی را قبل از دیدن گیرنده باز میکنند؛ GETای که آدمها را لغو اشتراک کند، لحظهی رسیدن کمپین، کل فهرست یک شرکت را بیصدا لغو میکرد و اولین نشانهاش برای مشتری، گزارش دسترسیای بود که فرو ریخته. POST /e/u همان مسیر یککلیک RFC 8058 است که خود سرویسدهندههای ایمیل صدا میزنند و هیچ مرحلهی تاییدی ندارد. GET /e/p صفحهی تنظیمات خود گیرنده است.
آنچه باید در سیاست حریم خصوصی خودتان بنویسید، دستکم این است: در ایمیلهای بازاریابی یک تصویر ردیاب هست؛ باز شدن پیام همراه با زمان، نشانی IP و مشخصات برنامهی ایمیل ثبت میشود؛ لینکهای داخل پیام شناسههایی حمل میکنند که فعالیت بعدی روی سایت خودتان را به همان پیام وصل میکند؛ و این کار از طریق یک پردازشگر انجام میشود.
آنچه در هر رویداد ذخیره میشود
نشانی IP کامل ذخیره میشود. ستونی به نام ip روی جدول رویدادها هست و هیچ ماسک، هیچ کوتاهکردن و هیچ هشی روی آن انجام نمیشود. اگر نصب طوری تنظیم شده باشد که به هدرهای پراکسی اعتماد کند، سمت چپترین مقدار X-Forwarded-For برداشته میشود، وگرنه نشانی خود اتصال.
از همان IP، کشور و استان و شهر استخراج و در ستونهای جدا ذخیره میشود، مگر اینکه SDK خودش مکان فرستاده باشد.
باقی چیزهایی که هر رویداد میتواند حمل کند و در انبار داده مینشیند: نشانی و مسیر و عنوان و ارجاعدهندهی صفحه، پارامترهای utm_*، نوع و مدل و سازندهی دستگاه، نام و نسخهی سیستمعامل، نام و نسخهی مرورگر، اپراتور شبکه، زبان، منطقهی زمانی، نسخهی اپ، و هر ویژگیای که خودتان روی رویداد گذاشتهاید.
رشتهی خام User-Agent روی جدول رویدادها ذخیره نمیشود؛ فقط چیزهایی که از آن استخراج میشود میمانند، بهعلاوهی پرچم ربات.
پروندهها اینها را نگه میدارند: ایمیل، شمارهی همراه، نام و نام خانوادگی، جنسیت، تاریخ تولد، کد ملی، شهر و استان و کشور، زبان، منطقهی زمانی، و نقشهی ویژگیهای خودتان. خروجی پروندهها عمداً ip و national_id را حمل نمیکند؛ گزارش و خروجی گرفتن را ببینید.
دیباگر رویداد در پنل، جریان زندهی بار خام ورودی را نشان میدهد: آدرس ایمیل، محتوای سفارش، و گاهی شمارهای که کسی نمیخواسته در یک ویژگی بگذارد. به همین دلیل دسترسی profile.read میخواهد، پاسخش Cache-Control: no-store دارد و پنجرهی ضبطش ۳۰ دقیقه است.
آنچه ضبط میکند فقط فراخوانیهای تکی است، یعنی POST /v1/track و همتاهایش، بهعلاوهی تحویل وبهوک. مسیر دستهای اصلاً ضبط نمیشود و هر SDK منتشرشدهای دسته میفرستد، پس نصب SDK در این صفحه چیزی نشان نمیدهد. برای اینکه ببینید رویدادهای یک اپ رسیدهاند یا نه، صفحهی اتصال همان اپ در پنل ابزار درست است.
بینامسازی
نقطهی پایانی بینامسازی وجود ندارد. هیچ حالتی نیست که سطر بماند و شناسهاش خالی شود. آنچه هست، دو چیز است: حذف یک نفر، و انقضا بر اساس سیاست.
تنها ابزار بینامسازی در کل پلتفرم، همان HMAC است که سابقهی حذف را نگه میدارد و در چه چیزی میماند شرحش آمد. کلیدش از کلید مخفی نصب گرفته میشود و آن کلید باید دستکم ۳۲ نویسه باشد، وگرنه سرویس اصلاً کلید نمیسازد و ثبت درخواست حذف ۵۰۳ میگیرد.
بازدیدکنندهی ناشناسی که هرگز خودش را معرفی نکرده، با هیچ درخواستی قابل حذف نیست. رویدادهای او user_id خالی دارند و تنها راه رسیدن به آنها، anonymous_idهایی است که در identity_map به یک شناسهی کاربر وصل شدهاند. اگر آن مرورگر هرگز به یک کاربر متصل نشده، هیچ شناسهای وجود ندارد که درخواست بتواند نامش را ببرد. آن رویدادها با سیاست نگهداری و با TTL چهارصدروزهی جدول از بین میروند، نه با یک درخواست حذف.
رباتها هم پاک نمیشوند، فقط با is_bot علامت میخورند و از گزارشها بیرون میمانند.
آنچه وجود ندارد
فهرست صادقانه، چون یک جای خالی که بدانید از یک جملهی قابلقبول که ندانید ارزش بیشتری دارد:
- هیچ نقطهی پایانی حریم خصوصی روی
api.segmentic.net. نه ثبت حذف، نه خواندن صف، نه خواندن یا تغییر سیاست نگهداری. همه فقط از پنل. - هیچ خروجی «همهی دادهی این یک نفر». تایملاین سقف ۱۰۰ رویداد دارد و خروجی پروندهها فیلتر تکنفره ندارد.
- هیچ فراخوان برگشتی یا وبهوکی وقتی یک حذف تمام میشود. باید صف را دوباره بخوانید.
- هیچ راهی برای حذف بازدیدکنندهی ناشناس متصلنشده.
- هیچ سیاست نگهداری برای یک نفر. سیاست فقط سطح حساب است.
- هیچ حالت بینامسازی بهجای حذف.
- حذف به امتیاز درگیری، امتیاز ریزش، پاسخهای نظرسنجی و صف تحویل رله نمیرسد. اگر تعهد شما پاکشدن کامل است، این چهار مورد امروز دستیاند.
- هیچ سطری در فهرست مسدودسازی برای شمارهی همراه حذفشده نوشته نمیشود. فقط ایمیل چنین محافظتی میگیرد.
- شمارشهای داخل گزارش حذف فقط همان جدولهایی را میشمارند که حذف به آنها دست زده، نه چیزی بیشتر.
برای اینکه یک آدم چطور خودش کانال و موضوع را عوض میکند، رضایت و لغو اشتراک را ببینید. برای اینکه چه چیزی اصلاً وارد رویداد میشود، رویدادها.