Mevcut platformdan Pushizo'ya göç
Pushizo, eski sağlayıcının kavram modelini (app, abonelik, kullanıcı/alias, tag, segment, şablon, journey) ve SDK yüzeyini (initialize → login → User.addTags → Notifications.requestPermission → Session.sendEvent)…
Pushizo, eski sağlayıcının kavram modelini (app, abonelik, kullanıcı/alias, tag, segment, şablon, journey) ve SDK yüzeyini (initialize → login → User.addTags → Notifications.requestPermission → Session.sendEvent) bilinçli olarak korur. Bu rehber mevcut bir eski sağlayıcı uygulamasını kesintisiz taşımanın adımlarını, alan/uç eşlemelerini ve SDK değişikliklerini anlatır.
Araçlar: npm run migrate:legacy (scripts/migrate-legacy.ts), CSV içe aktarıcı (packages/db/src/data-transfer.ts → mapLegacyPlayerRow), Postman koleksiyonu (api/postman_collection.json).
1. Genel plan
| Adım | Süre | Çıktı |
|---|---|---|
| 1. Hesap, organizasyon ve app oluşturma | 10 dk | app_… kimliği |
2. Kanal kimlik bilgileri (APNs .p8, FCM servis hesabı, VAPID, e-posta/SMS) | 30 dk | Kanallar active |
3. Player/abonelik göçü (migrate:legacy → CSV → içe aktarma) | dosya boyutuna göre | Kullanıcılar, tag'ler, iOS/Android/e-posta/SMS abonelikleri |
| 4. Sunucu entegrasyonu (REST → Pushizo public API / sunucu SDK'ları) | 1–3 gün | Bildirim, kullanıcı, olay çağrıları |
| 5. İstemci SDK değişimi (Web/iOS/Android/Flutter, React Native/Capacitor/Cordova/.NET MAUI/Unity) | 1–2 gün | Yeni sürüm yayını |
| 6. Çift yazma dönemi ve kesinti | 2–4 hafta | eski sağlayıcı kapatılır |
2. Hesap ve app oluşturma
- Dashboard'da kayıt olun (
/register), e-posta doğrulaması ve isteğe bağlı MFA'yı tamamlayın. Her hesap bir organizasyon altında çalışır; organizasyon eski sağlayıcıdaki "hesap", app ise eski sağlayıcı app'inin karşılığıdır. - Başlangıç sihirbazı (
/<org>/getting-started) ile app'i oluşturun ya da public API'den:POST /v1/apps(org geneliadminanahtarı gerekir). - Organizasyon → API anahtarları'ndan bir sunucu anahtarı (
hs_live_…) üretin. Kapsamlar eski sağlayıcı REST API anahtarından daha incedir:notifications:write,users:read,users:write,events:write,segments:read,analytics:read,admin. Göç içinadminkapsamlı, IP izin listeli ve bitiş tarihli bir anahtar önerilir. - Kanallar sayfasında eski sağlayıcıda kullandığınız kimlik bilgilerini aynen girin:
| eski sağlayıcı ayarı | Pushizo kanalı | Notlar |
|---|---|---|
Apple → .p8 anahtarı, Key ID, Team ID, Bundle ID | iOS Push (ios_push) | Ortam sandbox / production; aynı .p8 her iki tarafta çalışır. |
| Google → Firebase servis hesabı JSON | Android Push (android_push) | Aynı Firebase projesi kullanılır; cihaz token'ları geçerliliğini korur. |
| Web → Safari/Chrome ayarları | Web Push (web_push) | Pushizo kendi VAPID çiftini üretir; mevcut tarayıcı abonelikleri taşınamaz (bkz. §4.3). |
| E-posta (SendGrid vb.) | E-posta (email) | SMTP / Amazon SES / Resend; gönderici alan adını yeniden doğrulayın (SPF/DKIM/DMARC). |
| SMS (Twilio) | SMS (sms) | Twilio hesap bilgileri aynen; Netgsm de desteklenir. |
Her kanalda Doğrula düğmesi (POST /v1/apps/{app_id}/channels/{channel}/validate) sağlayıcıya test bağlantısı yapar.
3. Player (abonelik) göçü
3.1 npm run migrate:legacy
Araç eski sağlayıcı "View devices" API'sinden (GET /players, 300'lük sayfalar) ya da büyük uygulamalar için POST /players/csv_export ile cihazları çeker, Pushizo'nun legacy_players CSV biçimine yazar ve istenirse doğrudan içe aktarmayı başlatıp tamamlanana kadar izler.
# Yalnızca CSV üret
npm run migrate:legacy -- --source-app-id <SOURCE_APP_ID> --source-key <REST_API_KEY> --out players.csv
# Büyük uygulama: eski sağlayıcı csv_export (gz) kullan
npm run migrate:legacy -- --source-app-id <id> --source-key <key> --csv-export --out players.csv
# CSV üret ve Pushizo'ya içe aktar (iş tamamlanana kadar bekler)
npm run migrate:legacy -- --source-app-id <id> --source-key <key> \
--hs-api-url https://api.pushizo.com --hs-api-key hs_live_… --hs-app-id app_…
```text
Ortam değişkenleri de kabul edilir: `SOURCE_APP_ID`, `SOURCE_REST_API_KEY`, `HS_API_URL`, `HS_API_KEY`, `HS_APP_ID`. Çıktı stdout'a JSON olarak yazılır: `{ players, file, importId, importStatus, stats }`.
CSV başlıkları eski sağlayıcının dışa aktarma biçimiyle aynıdır (`id, identifier, session_count, language, timezone, game_version, device_os, device_type, device_model, ad_id, tags, last_active, playtime, amount_spent, created_at, invalid_identifier, badge_count, external_user_id, notification_types`). eski sağlayıcı panelinden indirdiğiniz "Export" dosyasını da aracı kullanmadan doğrudan yükleyebilirsiniz.
### 3.2 CSV'yi elle içe aktarma
```bash
curl -X POST "https://api.pushizo.com/v1/apps/app_…/imports" \
-H "Authorization: Bearer hs_live_…" \
-F type=legacy_players -F file=@players.csv
# → { "id": "imp_…", "status": "queued" }
curl "https://api.pushizo.com/v1/apps/app_…/imports/imp_…" -H "Authorization: Bearer hs_live_…"
# → { "status": "completed", "stats": { "processed", "created_users", "updated_users", "created_subscriptions", "skipped", "errors" }, "error_report_url": "…" }
```text
Dashboard eşdeğeri: **Kitle → Dışa / İçe Aktarım**. Dosya sınırı 50 MB ve 500.000 satırdır; daha büyük dosyaları bölün. İçe aktarma idempotenttir: aynı dosya ikinci kez yüklendiğinde kullanıcı ve abonelik yinelenmez.
### 3.3 Alan eşleme tablosu (`mapLegacyPlayerRow`)
| eski sağlayıcı sütunu | Pushizo alanı | Kural |
|------------------|------------------|-------|
| `external_user_id` | `external_id` | Boşsa ve `identifier` varsa anonim kullanıcı oluşturulur; ikisi de boşsa satır hata raporuna düşer. |
| `identifier` | abonelik `token` | Push token / e-posta / telefon. E-posta küçük harfe, telefon E.164'e normalize edilir; geçersizse satır atlanır. |
| `device_type` | abonelik `channel` | `0` → `ios_push`; `1`, `2`, `13` → `android_push`; `11` → `email`; `14` → `sms`; `5`, `7`, `8`, `9`, `10` (web) → yalnızca kullanıcı oluşturulur (bkz. aşağı). Diğer değerler: abonelik yok. |
| `invalid_identifier` = `t` | abonelik `status: invalid` | |
| `notification_types` < 0 | abonelik `status: unsubscribed` | Aksi halde `subscribed`. |
| `tags` (JSON) | kullanıcı `tags` | Değerler metne çevrilir (nesne/dizi JSON metni olur, `null` atlanır). Geçersiz JSON → satır hatası. |
| `language` | `properties.language`, `device.locale` | |
| `timezone` (UTC'den saniye) | `device.timezone` | Tam saat ise `Etc/GMT∓h` (UTC+3 → `Etc/GMT-3`), değilse boş. |
| `device_model`, `device_os`, `game_version` | `device.model`, `device.os_version`, `device.app_version` | |
| `session_count`, `created_at`, `last_active` | `session_count`, `first_seen_at`, `last_active_at` | |
| `id`, `ad_id`, `playtime`, `amount_spent`, `badge_count` | — | Aktarılmaz. |
**Web push abonelikleri CSV ile taşınamaz**: eski sağlayıcı dışa aktarımı `p256dh`/`auth` anahtarlarını içermez ve VAPID anahtar çifti değiştiği için tarayıcı abonelikleri yeniden oluşturulmalıdır. Web ziyaretçileri Pushizo Web SDK'sı yüklendiğinde (`autoResubscribe: true` varsayılanıyla) izin zaten verilmişse sessizce yeniden abone olur; bu yüzden web için çift yazma dönemini uzun tutun.
### 3.4 Göç sonrası kontrol
- **Abonelikler** sayfasında kanal dağılımını eski sağlayıcı "Audience" sayılarıyla karşılaştırın.
- Bir cihazı **Test cihazı** yapıp **Mesajlar → Test push gönder** ile gerçek teslimatı doğrulayın (eski sağlayıcıdan gelen APNs/FCM token'ları Pushizo'dan da çalışır).
- `error_report_url` dosyasındaki satırları inceleyin (`line, error, row`).
## 4. API eşleme tablosu
Kimlik doğrulama: eski sağlayıcı `Authorization: Basic <REST_API_KEY>` → Pushizo `Authorization: Bearer hs_live_…`. Hata zarfı `{ "error": { "code": "HS_…", "message", "details", "request_id" } }` ([hata kataloğu](/docs/errors)). Sayfalama eski sağlayıcıdaki `offset`/`limit` yerine `cursor`/`limit` (`next_cursor`, `has_more`).
### 4.1 Uçlar
| eski sağlayıcı REST | Pushizo public API | Fark |
|----------------|----------------------|------|
| `POST /notifications` | `POST /v1/notifications` | Gövdede `app_id`; yanıt `202 { id, status, recipients_estimate }`. Idempotency için `Idempotency-Key` başlığı ya da `idempotency_key` alanı (eski sağlayıcı `external_id`). |
| `GET /notifications?app_id=` | `GET /v1/notifications?app_id=` | Cursor sayfalama, `status` filtresi. |
| `GET /notifications/{id}` | `GET /v1/notifications/{message_id}` | `counts` nesnesi (`queued, sent, delivered, failed, opened, clicked, suppressed…`). |
| `DELETE /notifications/{id}` | `DELETE /v1/notifications/{message_id}` | Yalnızca zamanlanmış/kuyruktaki mesaj iptal edilir. |
| `GET /notifications/{id}/history` | `GET /v1/notifications/{message_id}/history` | CSV işi; tamamlanınca `download_url`. |
| `POST /notifications/{id}/export_events` | `POST /v1/notifications/{message_id}/export_events` | CSV işi; `download_url`. |
| `GET /players` (view devices) | `GET /v1/apps/{app_id}/users` + `GET /v1/apps/{app_id}/subscriptions/{id}` | Cihaz kavramı yerine **kullanıcı** ve **abonelik**; aboneliğin sahibi `GET …/subscriptions/{id}/user/identity`. |
| `GET /players/{id}` | `GET /v1/apps/{app_id}/subscriptions/{subscription_id}` | Alanlar: `channel`, `status`, `token_masked`, `device`, `user_id`. |
| `POST /players` / `PUT /players/{id}` | `POST /v1/apps/{app_id}/users` (aboneliklerle) / `PATCH …/subscriptions/{id}` | Sunucu tarafı cihaz kaydı için `users.upsert` + `subscriptions[]`. |
| `PUT /players/{id}` (tag güncelleme) | `PATCH /v1/apps/{app_id}/users/by/{label}/{value}` | Merge-patch `tags`, `properties`, `deltas`. `null` değer tag'i siler. |
| `POST /players/csv_export` | `POST /v1/apps/{app_id}/exports` (`type: users` \| `subscriptions` \| `events` \| `deliveries`) | `GET …/exports/{id}` → `download_url`. |
| `GET /users/by/{alias_label}/{alias_id}` | `GET /v1/apps/{app_id}/users/by/{label}/{value}` | Aynı yol; `hs_id` eski sağlayıcı `eski kimlik`'nin karşılığıdır. |
| `POST /users` | `POST /v1/apps/{app_id}/users` | `external_id`, `tags`, `properties`, `language`, `subscriptions[]`. |
| `PATCH /users/by/{label}/{value}` | `PATCH /v1/apps/{app_id}/users/by/{label}/{value}` | |
| `DELETE /users/by/{label}/{value}` | `DELETE /v1/apps/{app_id}/users/by/{label}/{value}` | Abonelikler silinir, olaylar anonimleşir. |
| `GET/PATCH /users/by/{label}/{value}/identity` | `GET/PATCH /v1/apps/{app_id}/users/by/{label}/{value}/identity` | Alias ekleme; `DELETE …/identity/{alias_label}` kaldırır. |
| `POST /users/by/{label}/{value}/subscriptions` | `POST /v1/apps/{app_id}/users/by/{label}/{value}/subscriptions` | `{ channel, token, device? }`. |
| `PATCH /subscriptions/{id}` | `PATCH /v1/apps/{app_id}/subscriptions/{subscription_id}` | `opted_in`, `device`, `test_device`. |
| `DELETE /subscriptions/{id}` | `DELETE /v1/apps/{app_id}/subscriptions/{subscription_id}` | |
| `PATCH /subscriptions_by_token/{token_type}/{token}` | `PATCH /v1/apps/{app_id}/subscriptions_by_token/{token_type}/{token}` | eski sağlayıcı tür adları (`iOSPush`, `AndroidPush`, `ChromePush`, `FirefoxPush`, `SafariPush`, `Email`, `SMS`) da kabul edilir. |
| `POST /subscriptions/{id}/owner` | `POST /v1/apps/{app_id}/subscriptions/{subscription_id}/owner` | `{ external_id }`. |
| `GET /subscriptions/{id}/user/identity` | `GET /v1/apps/{app_id}/subscriptions/{subscription_id}/user/identity` | |
| `POST /apps/{app_id}/custom_events` | `POST /v1/apps/{app_id}/events` | `[{ external_id \| subscription_id, name, properties, value, occurred_at }]`, en fazla 500. |
| `GET /apps/{app_id}/outcomes` | `GET /v1/apps/{app_id}/analytics/conversions` | Dönüşüm olayı `event_definitions.is_conversion` ile işaretlenir (`PATCH …/events/definitions/{name}`). |
| `GET/POST /apps/{app_id}/segments`, `DELETE …/segments/{id}` | `GET/POST /v1/apps/{app_id}/segments`, `PATCH/DELETE …/{id}` | Filtre AST (`{ field, op, value }` koşulları, `all`/`any`/`none` grupları; ör. `tags.plan`, `properties.city`, `language`, `event`); `POST …/segments/preview` sayım + örnek kullanıcılar. |
| `GET/POST /templates` | `GET/POST /v1/apps/{app_id}/templates`, `PATCH/DELETE …/{id}`, `POST …/{id}/render` | Liquid; sürüm geçmişi `…/versions`, başka app'e kopya `…/copy_to_app`. |
| `GET /apps`, `GET /apps/{id}` | `GET /v1/apps`, `GET /v1/apps/{app_id}` | `PATCH /v1/apps/{app_id}` ayarları günceller. |
| `POST /apps` | `POST /v1/apps` | Org geneli `admin` anahtarı. |
| Journeys (yalnızca panel) | `GET/POST /v1/apps/{app_id}/journeys`, `…/draft`, `…/publish`, `…/runs` | API ile tasarlanabilir ve tetiklenebilir ([journey'ler](/docs/journeys)). |
| In-App Messages (yalnızca panel) | `GET/POST /v1/apps/{app_id}/in-app-messages` | Blok tabanlı, HTML ([HTML in-app](/docs/channels/html-in-app)) ve carousel; mobil ve web. |
| Live Activities: `POST /apps/{app_id}/activities/activity/{activity_type}` (push-to-start), `POST /apps/{app_id}/live_activities/{activity_id}/notifications` (güncelle/bitir) | `POST /v1/apps/{app_id}/live-activities/{activity_type}/start`, `…/update`, `…/end`; tür kayıtları `…/live-activities/types` | Journey düğümü de var ([Live Activities](/docs/channels/live-activities)). |
Postman koleksiyonu (`api/postman_collection.json`) tüm bu uçları `{{baseUrl}}`, `{{apiKey}}`, `{{appId}}` değişkenleriyle içerir.
### 4.2 `POST /notifications` gövdesi
Hedefleme alanları Pushizo'da `target` nesnesi altında toplanır; içerik alanlarının çoğu aynı adı taşır.
| eski sağlayıcı | Pushizo | Not |
|-----------|-----------|-----|
| `included_segments` / `excluded_segments` | `target.included_segments` / `target.excluded_segments` | Segment kimlikleri (`seg_…`) ya da adları. |
| `include_aliases: { external_id: [] }` | `target.include_aliases` veya `target.include_external_ids` | En fazla 2000. |
| `include_subscription_ids` (eski `include_player_ids`) | `target.include_subscription_ids` | |
| `include_email_tokens` | `target.email_to` | Aboneliği olmayan adres için anonim kullanıcı + abonelik oluşturulur. |
| `include_phone_numbers` | `target.include_phone_numbers` | E.164. |
| `filters` | `target.filters` | Segment AST (eski sağlayıcı `field/relation/value` yerine `{ field: "tags.plan", op: "eq", value: "pro" }` ve `all`/`any`/`none` grupları). |
| `target_channel: "push" \| "email" \| "sms"` | `target.channels: ["ios_push", "android_push", "web_push", "email", "sms"]` | Verilmezse içerikten çıkarılır; SMS yalnızca açıkça. |
| `isIos`, `isAndroid`, `isAnyWeb` | `target.channels` | |
| `headings`, `contents`, `subtitle`, `url`, `app_url`, `data`, `buttons`, `big_picture`, `ios_attachments`, `ios_sound`, `ios_category`, `thread_id`, `ios_relevance_score`, `target_content_identifier`, `android_channel_id`, `android_group`, `android_sound`, `small_icon`, `large_icon`, `android_accent_color`, `android_visibility`, `chrome_web_icon`, `chrome_web_image`, `chrome_web_badge`, `web_push_topic`, `web_buttons`, `content_available`, `mutable_content`, `template_id`, `custom_data`, `send_after`, `delayed_option`, `delivery_time_of_day`, `ttl`, `priority`, `collapse_id`, `email_subject`, `email_body`, `email_preheader`, `email_from_name`, `email_from_address`, `email_reply_to_address`, `disable_email_click_tracking`, `sms_from`, `sms_media_urls`, `include_unsubscribed`, `name` | aynı ad | `priority` eski sağlayıcıdaki gibi `5` (normal) ya da `10` (yüksek); `ttl` saniye. |
| `ios_badgeType`, `ios_badgeCount` | `ios_badge_type`, `ios_badge_count` | snake_case. |
| `ios_interruption_level` | `ios_interruption_level` | `passive`, `active`, `time-sensitive`, `critical` ([critical alert rehberi](/docs/channels/ios-critical-alerts)). |
| `external_id` (idempotency) | `idempotency_key` ya da `Idempotency-Key` başlığı | Aynı anahtar + aynı gövde aynı yanıtı döndürür; farklı gövde `409 HS_IDEMPOTENCY_CONFLICT`. |
| `send_after` + `delayed_option: "timezone"` | aynı | `delivery_time_of_day: "09:00"`. |
| `throttle_rate_per_minute` | `throttle_rate_per_minute` | |
| `email_mjml` | `email_mjml` | eski sağlayıcıda yok; MJML derlemesi Pushizo'ya özgüdür. |
| — | `add_to_inbox`, `inbox_ttl`, `category`, `labels`, `one_per_user`, `frequency_cap_exempt`, `quiet_hours_exempt`, `require_interaction` | Pushizo ek alanları. |
Örnek (Node sunucu SDK'sı):
```ts
import { Pushizo } from '@pushizo/node';
const hs = new Pushizo({ apiKey: process.env.HS_API_KEY!, appId: process.env.HS_APP_ID, baseUrl: 'https://api.pushizo.com' });
await hs.notifications.create({
idempotency_key: `order-${order.id}`,
target: { include_aliases: { external_id: [userId] } },
headings: { tr: 'Siparişiniz yola çıktı', en: 'Your order has shipped' },
contents: { tr: 'Kargo takip bağlantısı içeride', en: 'Tracking link inside' },
url: `https://pushizo.com/orders/${order.id}`,
data: { order_id: order.id },
});
```text
### 4.3 Webhook'lar
eski sağlayıcı "Event Streams"in karşılığı Pushizo **webhook endpoint'leri** (`/v1/apps/{app_id}/webhooks`) ve aynı adlı **Event Streams** gövde şablonu/olay filtresidir ([Event Streams](/docs/analytics/event-streams)); olaylar HMAC imzalıdır (`Pushizo-Signature: t=…,v1=…`): `delivery.*`, `subscription.*`, `user.*`, `event.received`, `conversion.created`, `campaign.*`, `journey.*`, `in_app.*`. İmza doğrulama ve yeniden deneme planı için [webhook'lar](/docs/webhooks).
## 5. SDK değişim rehberi
Tüm istemci SDK'larında gizli anahtar yoktur; yalnızca **App ID** (ve kendi API adresiniz) kullanılır. `identity verification` zorunluysa `login` ikinci parametre olarak sunucunuzun ürettiği kanıtı alır: eski sağlayıcıdaki gibi **ES256 JWT** (`jwt` modu) ya da `identity_hash = HMAC-SHA256(sdk_secret, external_id)` (`hash` modu); ayrıntı [JWT ile kimlik doğrulama](/docs/users/identity-verification-jwt). eski sağlayıcıdan farklı olarak web SDK'sı da desteklenir.
### 5.1 Web
| eski sağlayıcı v16 (Web) | Pushizo Web SDK (`@pushizo/web`) |
|---------------------|-----------------------------------------|
| `<script src="eski web SDK betiği">` + `eski SDK başlatma çağrısı` | `<script src="https://api.pushizo.com/sdk-web/PushizoSDK.js" async>` + `PushizoDeferred.push(async (Pushizo) => { await Pushizo.init({ appId: 'app_…', apiUrl: 'https://api.pushizo.com' }) })` |
| `eski sağlayıcıSDKWorker.js` site kökünde | `PushizoSDKWorker.js` site kökünde (`importScripts(apiUrl + '/sdk-web/PushizoSDKWorker.js')`); `serviceWorkerPath` / `serviceWorkerScope` seçenekleri aynı anlamda |
| `eski sağlayıcı.login(externalId, jwtToken?)` | `Pushizo.login(externalId, identityHash?)` |
| `eski sağlayıcı.logout()` | `Pushizo.logout()` |
| `eski sağlayıcı.User.addTag / addTags / removeTag / removeTags / getTags` | aynı adlar: `Pushizo.User.addTag / addTags / removeTag / removeTags / getTags` |
| `eski sağlayıcı.User.addAlias / addAliases / removeAlias` | aynı adlar |
| `eski sağlayıcı.User.addEmail / removeEmail / addSms / removeSms` | aynı adlar (adres istemcide normalize edilir) |
| `eski sağlayıcı.User.setLanguage` | `Pushizo.User.setLanguage` |
| `eski sağlayıcı.User.PushSubscription.optIn / optOut / id / token / optedIn` + `addEventListener('change')` | `Pushizo.User.pushSubscription.optIn / optOut / id / optedIn / state` + `addObserver(cb)` (geri dönen fonksiyon dinleyiciyi kaldırır) |
| `eski sağlayıcı.Notifications.requestPermission()` / `permission` | `Pushizo.Notifications.requestPermission()` / `permission` (`'default' \| 'granted' \| 'denied' \| 'unsupported'`) |
| `eski sağlayıcı.Notifications.addEventListener('click', cb)` | `Pushizo.Notifications.addClickListener(({ notification, result }) => …)` |
| `eski sağlayıcı.Notifications.addEventListener('foregroundWillDisplay', cb)` | `Pushizo.Notifications.addForegroundLifecycleListener((e) => e.preventDefault())` |
| `eski sağlayıcı.Slidedown.promptPush / promptEmail / promptSms / promptPushCategories` | `Pushizo.Slidedown.promptPush / promptEmail / promptSms / promptEmailAndSms` (kategori tercihleri için `User.setPreferences`) |
| `promptOptions.slidedown`, `welcomeNotification`, `notificationClickHandlerMatch/Action`, `notifyButton` | `slidedown`, `welcomeNotification`, `notificationClickHandlerMatch` / `notificationClickHandlerAction`, `bell` (init seçenekleri) |
| `eski sağlayıcı.Session.sendOutcome / sendUniqueOutcome / sendOutcomeWithValue` | `Pushizo.Session.sendEvent(name, properties?, value?)` |
| `eski sağlayıcı.User.PushSubscription` bildirim kutusu yok | `Pushizo.Inbox.list / markRead / markAllRead / archive / unreadCount / addObserver` |
| In-app (yalnızca mobil) | `Pushizo.InAppMessages.addTrigger / addTriggers / removeTrigger / clearTriggers / setScreen / paused / addClickListener / addLifecycleListener` (web dahil) |
| `eski sağlayıcı.setConsentRequired(true)` + `setConsentGiven(true)` | `init({ requiresPrivacyConsent: true })` + `Pushizo.setConsentGiven(true)` |
| `eski sağlayıcı.Debug.setLogLevel` | `Pushizo.Debug.setLogLevel('debug')` |
Ayrıntı: [Web SDK](/docs/sdk/web), çatı entegrasyonları: [React](/docs/integrations/react), [Vue](/docs/integrations/vue), [Angular](/docs/integrations/angular), iOS Safari PWA: [iOS web push](/docs/channels/ios-pwa-web-push).
### 5.2 iOS
| eski sağlayıcı iOS SDK 5 | Pushizo iOS SDK (`Pushizo`, `PushizoExtension`) |
|---------------------|----------------------------------------------------------|
| `eski sağlayıcı.initialize("APP_ID", withLaunchOptions:)` | `Pushizo.initialize("app_…", launchOptions:) { options in options.apiUrl = "https://api.pushizo.com" }` |
| Swizzling ile token | Swizzling yok: `didRegisterForRemoteNotificationsWithDeviceToken` içinde `Pushizo.setDeviceToken(deviceToken)`, hata durumunda `Pushizo.setDeviceTokenError(error)` |
| `eski sağlayıcı.login("id")` / `logout()` | `Pushizo.login("id", identityHash: nil)` / `Pushizo.logout()` |
| `eski sağlayıcı.User.addTag/addTags/removeTag/getTags/setLanguage` | aynı adlar |
| `eski sağlayıcı.User.addEmail/addSms/removeEmail/removeSms` | aynı adlar |
| `eski sağlayıcı.User.pushSubscription.optIn()/optOut()/id/token/optedIn` + `addObserver` | `Pushizo.User.pushSubscription.optIn()/optOut()/id/token`, `await state()`, `addObserver { state in }` |
| `eski sağlayıcı.Notifications.requestPermission(_:fallbackToSettings:)` | `Pushizo.Notifications.requestPermission { granted in }` (`await permission()` ile okuma) |
| `eski sağlayıcı.Notifications.addPermissionObserver` | `addPermissionObserver { granted in }` → `HSListenerToken`; `removePermissionObserver(token)` |
| `addClickListener` / `addForegroundLifecycleListener` | aynı adlar; belirteç döndürür (`removeClickListener(token)`) |
| `eski sağlayıcıNotificationServiceExtension` | `PushizoNotificationServiceExtension(appId:apiUrl:appGroup:)` → `didReceive`, `serviceExtensionTimeWillExpire` |
| `eski sağlayıcı.InAppMessages.addTrigger/removeTrigger/clearTriggers/paused` | aynı adlar + `setScreen`, `addClickListener`, `addLifecycleListener` |
| `eski sağlayıcı.Session.sendOutcome(_:)` | `Pushizo.Session.sendEvent("name", properties:, value:)` |
| `eski sağlayıcı.setConsentRequired/setConsentGiven` | `options.requiresPrivacyConsent = true` + `Pushizo.setConsentGiven(true)` |
| `eski sağlayıcı.Notifications.clearAll()` | `Pushizo.Notifications.clearAll()`, `removeGroupedNotifications`, `removeNotification(identifier:)` |
| Live Activities | `Pushizo.LiveActivities.register(DeliveryAttributes.self)` (iOS 16.1+; push-to-start 17.2+). Ayrıntı: [iOS SDK](/docs/sdk/ios). |
Aynı APNs `.p8` anahtarı kullanıldığından mevcut cihaz token'ları Pushizo'dan da teslim alır; SDK ilk açılışta token'ı `POST /subscriptions` ile kaydeder (göçte aktarılmış abonelikle `(app, channel, token)` üzerinden birleşir). Ayrıntı: [iOS SDK](/docs/sdk/ios).
### 5.3 Android
| eski sağlayıcı Android SDK 5 | Pushizo Android SDK (`com.pushizo:pushizo-android`) |
|-------------------------|---------------------------------------------------------------|
| `eski sağlayıcı.initWithContext(this, "APP_ID")` | `Pushizo.initialize(this, "app_…", Pushizo.Options(apiUrl = "https://api.pushizo.com"))` (Application `onCreate`) |
| `eski sağlayıcı.login("id")` / `logout()` | `Pushizo.login("id", identityHash = null)` / `Pushizo.logout()` |
| `eski sağlayıcı.User.addTag/addTags/removeTag/getTags/setLanguage` | aynı adlar |
| `eski sağlayıcı.User.addEmail/addSms/removeEmail/removeSms` | aynı adlar (`emails`, `smsNumbers` listeleri) |
| `eski sağlayıcı.User.pushSubscription.optIn()/optOut()/id/token/optedIn` + `addObserver` | aynı adlar (`state` ek) |
| `eski sağlayıcı.Notifications.requestPermission(fallbackToSettings)` (coroutine) | `Pushizo.Notifications.requestPermission(activity)` + `onRequestPermissionsResult` içinde `Pushizo.Notifications.onPermissionResult()` (`PERMISSION_REQUEST_CODE`) |
| `addPermissionObserver` | `addPermissionObserver(observer)` / `removePermissionObserver(observer)` |
| `addClickListener` / `addForegroundLifecycleListener` | aynı adlar |
| `eski sağlayıcı.InAppMessages.addTrigger/…/paused` | aynı adlar + `setScreen`, `addClickListener`, `addLifecycleListener` |
| `eski sağlayıcı.Session.sendOutcome` | `Pushizo.Session.sendEvent("name", mapOf(...), value)` |
| `eski sağlayıcı.consentRequired = true` / `consentGiven = true` | `Options(requiresPrivacyConsent = true)` + `Pushizo.setConsentGiven(true)` |
| `eski sağlayıcı.Notifications.clearAllNotifications()` | `Pushizo.Notifications.clearAll()`, `removeNotification`, `removeGroupedNotifications` |
| Bildirim kanalları (panelden) | Dashboard → Ayarlar → Android bildirim kategorileri (`android_channels`); `android_channel_id` ile seçilir |
Aynı Firebase projesi kullanıldığı sürece FCM token'ları değişmez. Ayrıntı: [Android SDK](/docs/sdk/android).
### 5.4 Flutter
| `eski Flutter paketi` | `pushizo_flutter` |
|---------------------|----------------------|
| `eski sağlayıcı.initialize("APP_ID")` | `await Pushizo.initialize('app_…', apiUrl: 'https://api.pushizo.com')` |
| `eski sağlayıcı.login/logout` | `Pushizo.login(externalId, identityHash: null)` / `Pushizo.logout()` |
| `eski sağlayıcı.User.addTags/removeTag/getTags/setLanguage` | aynı adlar (hepsi `Future`) |
| `eski sağlayıcı.User.addEmail/addSms/removeEmail/removeSms` | aynı adlar |
| `eski sağlayıcı.User.pushSubscription.optIn/optOut/id/token/optedIn` + `addObserver` | `Pushizo.User.pushSubscription.optIn()/optOut()`, `await state`, `changes.listen(...)` |
| `eski sağlayıcı.Notifications.requestPermission(true)` | `await Pushizo.Notifications.requestPermission()` |
| `eski sağlayıcı.Notifications.addPermissionObserver` | `Pushizo.Notifications.addPermissionObserver(fn)` ya da `permissionStream.listen(...)` |
| `addClickListener` / `addForegroundWillDisplayListener` | `Pushizo.Notifications.clickStream.listen(...)` / `foregroundStream.listen(...)` |
| `eski sağlayıcı.InAppMessages.addTrigger/…/paused(bool)` | aynı adlar + `setScreen`, `setPaused(bool)`, `clickStream`, `lifecycleStream`, `setNavigatorKey` |
| `eski sağlayıcı.Session.sendOutcome` | `Pushizo.Session.sendEvent(name, properties:, value:)` |
| `eski sağlayıcı.consentRequired(true)` / `consentGiven(true)` | `initialize(..., requiresPrivacyConsent: true)` + `Pushizo.setConsentGiven(true)` |
| — | `Pushizo.Inbox.list/markRead/markAllRead/archive/unreadCount`, `Pushizo.User.getPreferences/setPreferences` |
Ayrıntı: [Flutter SDK](/docs/sdk/flutter).
### 5.5 Köprü SDK'ları için ortak notlar
Aşağıdaki tablolarda eski sağlayıcı sütunu ilgili paketin **v5** API adlarını esas alır; daha eski (v3/v4) bir eski sağlayıcı sürümünden geçiyorsanız önce çağrıları v5 karşılıklarına eşleyin. Tüm köprü SDK'larında:
- Paket değişimi tek seferdedir: eski sağlayıcı paketini (ve Expo için `eski Expo eklentisi`) kaldırıp Pushizo paketini ekleyin, yerel projeyi yeniden üretin (`pod install`, `npx cap sync`, `cordova prepare`, `expo prebuild`). Pushizo iOS'ta `UNUserNotificationCenter` delegesini üstlendiğinden aynı sürümde iki push SDK'sı birlikte bulunmamalıdır.
- `initialize` App ID'nin yanında kendi API adresinizi (`apiUrl`) alır; REST API anahtarı istemciye gömülmez.
- eski sağlayıcı "outcome" çağrıları (`addOutcome`, `addUniqueOutcome`, `addOutcomeWithValue` ve C# karşılıkları) tek bir `Session.sendEvent(name, properties?, value?)` çağrısına dönüşür; aynı ad in-app `event` tetiklerini de çalıştırır. Tekillik (unique) gerekiyorsa uygulamada ya da journey/segment koşulunda sağlayın.
- Bildirim kutusu (`Inbox`) ve kategori tercihleri (`User.getPreferences` / `setPreferences`) eski sağlayıcıda karşılığı olmayan eklemelerdir (bkz. 5.11).
### 5.6 React Native
| `eski React Native paketi` 5 | `@pushizo/react-native` |
|----------------------------|----------------------------|
| Expo: `["eski Expo eklentisi", { "mode": "development" }]` | `["@pushizo/react-native", { "mode": "development", "appId": "app_…" }]` (plugin pakete dahildir; NSE hedefi `PushizoNotificationServiceExtension`) |
| Bare iOS: token swizzling ile alınır | Swizzling yok: `AppDelegate` içinde `Pushizo.setDeviceToken(deviceToken)` / `Pushizo.setDeviceTokenError(error)` (Expo plugin otomatik ekler) |
| `eski sağlayıcı.initialize('APP_ID')` | `await Pushizo.initialize('app_…', { apiUrl: 'https://api.pushizo.com' })` |
| `eski sağlayıcı.login(externalId)` / `eski sağlayıcı.logout()` | `Pushizo.login(externalId, identityHash?)` / `Pushizo.logout()` |
| `eski sağlayıcı.User.addTag / addTags / removeTag / removeTags / getTags` | aynı adlar |
| `eski sağlayıcı.User.addAlias / addAliases / removeAlias` | aynı adlar |
| `eski sağlayıcı.User.addEmail / removeEmail / addSms / removeSms`, `setLanguage` | aynı adlar |
| `eski sağlayıcı.User.getExternalId()` / `getLegacyId()` | `Pushizo.User.getExternalId()` / `getPushizoId()` |
| `eski sağlayıcı.User.pushSubscription.getIdAsync / getTokenAsync / getOptedInAsync / optIn / optOut` + `addEventListener('change', …)` | aynı adlar (`{ previous, current }`) |
| `eski sağlayıcı.Notifications.requestPermission(fallbackToSettings)` | `Pushizo.Notifications.requestPermission()` |
| `eski sağlayıcı.Notifications.getPermissionAsync()` | `Pushizo.Notifications.getPermissionAsync()` (eşzamanlı son değer: `permission`) |
| `eski sağlayıcı.Notifications.addEventListener('click' \| 'foregroundWillDisplay' \| 'permissionChange', …)` | aynı olay adları; `e.preventDefault()` ve `e.getNotification().display()` aynı |
| `eski sağlayıcı.Notifications.clearAll()` | `Pushizo.Notifications.clearAll()` |
| `eski sağlayıcı.InAppMessages.addTrigger / addTriggers / removeTrigger / removeTriggers / clearTriggers / setPaused / getPaused` | aynı adlar + `getTriggers`, `setScreen` |
| `eski sağlayıcı.InAppMessages.addEventListener('click' \| 'willDisplay' \| 'didDisplay' \| 'willDismiss' \| 'didDismiss', …)` | aynı olay adları; olay gövdesi `{ messageId, actionId, url }` / `{ messageId }` |
| `eski sağlayıcı.Session.addOutcome / addUniqueOutcome / addOutcomeWithValue` | `Pushizo.Session.sendEvent(name, properties?, value?)` |
| `eski sağlayıcı.setConsentRequired(true)` + `setConsentGiven(true)` | `initialize(…, { requiresPrivacyConsent: true })` + `Pushizo.setConsentGiven(true)` |
| `eski sağlayıcı.Debug.setLogLevel(LogLevel.Verbose)` | `Pushizo.Debug.setLogLevel(LogLevel.Debug)` |
| — | `Pushizo.Inbox.*`, `Pushizo.User.getPreferences / setPreferences` |
Ayrıntı: [React Native SDK](/docs/sdk/react-native).
### 5.7 Capacitor (Ionic)
eski sağlayıcı Ionic/Capacitor uygulamalarında `eski Cordova eklentisi` paketini kullanır; Pushizo'da ayrı bir Capacitor eklentisi vardır (web'de `@pushizo/web`'e devreder).
| `eski Cordova eklentisi` 5 (Capacitor) | `@pushizo/capacitor` |
|------------------------------------------|-------------------------|
| `npm install eski Cordova eklentisi` | `npm install @pushizo/capacitor` (+ web için `@pushizo/web`), `npx cap sync` |
| iOS token swizzling ile alınır | `AppDelegate.swift` içinde `capacitorDidRegisterForRemoteNotifications` / `capacitorDidFailToRegisterForRemoteNotifications` bildirimlerini gönderin |
| `eski sağlayıcı.initialize('APP_ID')` | `await Pushizo.initialize('app_…', { apiUrl: 'https://api.pushizo.com' })` |
| `eski sağlayıcı.login / logout` | `Pushizo.login(externalId, identityHash?)` / `Pushizo.logout()` (Promise) |
| `eski sağlayıcı.User.addTag / addTags / removeTag / removeTags / getTags`, `addAlias / addAliases / removeAlias`, `addEmail / removeEmail / addSms / removeSms`, `setLanguage` | aynı adlar (hepsi Promise) |
| `eski sağlayıcı.User.pushSubscription.getIdAsync / getTokenAsync / getOptedInAsync / optIn / optOut` | `Pushizo.User.pushSubscription.getId / getToken / getOptedIn / getState / optIn / optOut` |
| `eski sağlayıcı.User.pushSubscription.addEventListener('change', cb)` | `Pushizo.User.pushSubscription.addObserver((state) => …)` (dönen fonksiyon aboneliği kaldırır) |
| `eski sağlayıcı.Notifications.requestPermission(true)` | `await Pushizo.Notifications.requestPermission()` |
| `eski sağlayıcı.Notifications.addEventListener('click', cb)` | `Pushizo.Notifications.addClickListener(({ notification, result }) => …)` |
| `eski sağlayıcı.Notifications.addEventListener('foregroundWillDisplay', cb)` | `Pushizo.Notifications.addForegroundLifecycleListener((e) => e.preventDefault())`; erteleyip göstermek için `await e.display()` |
| `eski sağlayıcı.Notifications.addEventListener('permissionChange', cb)` | `Pushizo.Notifications.addPermissionObserver(({ permission }) => …)` |
| `eski sağlayıcı.Notifications.clearAll()` | `Pushizo.Notifications.clearAll()`, `removeGroupedNotifications`, `removeNotification` (yalnızca Android) |
| `eski sağlayıcı.InAppMessages.addTrigger / addTriggers / removeTrigger / removeTriggers / clearTriggers / setPaused / getPaused` | aynı adlar + `getTriggers`, `setScreen` |
| `eski sağlayıcı.InAppMessages.addEventListener('click' \| 'willDisplay' \| …, cb)` | `Pushizo.InAppMessages.addClickListener(cb)` / `addLifecycleListener({ willDisplay, didDisplay, willDismiss, didDismiss })` |
| `eski sağlayıcı.Session.addOutcome / addUniqueOutcome / addOutcomeWithValue` | `Pushizo.Session.sendEvent(name, properties?, value?)` |
| `eski sağlayıcı.setConsentRequired(true)` + `setConsentGiven(true)` | `initialize(…, { requiresPrivacyConsent: true })` + `await Pushizo.setConsentGiven(true)` |
| `eski sağlayıcı.Debug.setLogLevel(…)` | `await Pushizo.Debug.setLogLevel('debug')` |
Ayrıntı: [Capacitor SDK](/docs/sdk/capacitor).
### 5.8 Cordova
| `eski Cordova eklentisi` 5 | `cordova-plugin-pushizo` |
|------------------------------|-----------------------------|
| `cordova plugin add eski Cordova eklentisi`, `window.plugins.eski sağlayıcı` | `cordova plugin add cordova-plugin-pushizo`, `window.Pushizo` (ve `window.plugins.Pushizo`) |
| Android Gradle ayarı gerekmez | `config.xml` → `GradlePluginKotlinEnabled` ve `GradlePluginGoogleServicesEnabled` (köprü Kotlin'dir) |
| `eski sağlayıcı.initialize('APP_ID')` (`deviceready` içinde) | `await Pushizo.initialize('app_…', { apiUrl: 'https://api.pushizo.com' })` |
| Diğer tüm çağrılar | Capacitor tablosuyla (5.7) birebir aynı; tüm metotlar Promise döndürür, hatalar `PushizoError` (`code`) |
iOS'ta APNs token geri çağrıları eklenti tarafından AppDelegate'e çalışma zamanında eklenir; ek kod gerekmez. Ayrıntı: [Cordova SDK](/docs/sdk/cordova).
### 5.9 .NET MAUI
| `eski sağlayıcıSDK.DotNet` 5 | `Pushizo.Maui` |
|-------------------------|-------------------|
| `dotnet add package eski sağlayıcıSDK.DotNet`, `using eski sağlayıcıSDK.DotNet;` | `dotnet add package Pushizo.Maui`, `using PushizoSdk.Maui;` |
| `eski sağlayıcı.Initialize("APP_ID")` | `builder.UsePushizo("app_…", o => o.ApiUrl = "https://api.pushizo.com")` (`MauiProgram.cs`) ya da `Pushizo.Initialize("app_…", options)` |
| iOS token swizzling ile alınır | `AppDelegate` içinde `PushizoIos.RegisteredForRemoteNotifications(deviceToken)` / `FailedToRegisterForRemoteNotifications(error)` |
| `eski sağlayıcı.Login("id")` / `eski sağlayıcı.Logout()` | `Pushizo.Login("id", identityHash)` / `Pushizo.Logout()` |
| `eski sağlayıcı.User.AddTag / AddTags / RemoveTag / RemoveTags`, `AddAlias / AddAliases / RemoveAlias`, `AddEmail / RemoveEmail / AddSms / RemoveSms` | aynı adlar + `GetTags()`, `SetLanguage("tr")` |
| `eski sağlayıcı.User.PushSubscription.OptIn() / OptOut() / Id / Token / OptedIn` + `Changed` | aynı adlar (`e.Previous`, `e.Current`) |
| `await eski sağlayıcı.Notifications.RequestPermissionAsync(true)` | `await Pushizo.Notifications.RequestPermissionAsync(fallbackToSettings: true)` |
| `eski sağlayıcı.Notifications.Clicked / ForegroundWillDisplay / PermissionChanged` | aynı olay adları (`EventHandler`); tıklamada `e.Notification`, `e.ActionId`, `e.Url`; ön planda `e.PreventDefault()` |
| `eski sağlayıcı.Notifications.ClearAllNotifications()` | `Pushizo.Notifications.ClearAll()` |
| `eski sağlayıcı.InAppMessages.AddTrigger / AddTriggers / RemoveTrigger / RemoveTriggers / ClearTriggers / Paused` + `Clicked / WillDisplay / DidDisplay / WillDismiss / DidDismiss` | aynı adlar + `GetTriggers()`, `SetScreen(name)` |
| `eski sağlayıcı.Session.AddOutcome / AddUniqueOutcome / AddOutcomeWithValue` | `Pushizo.Session.SendEvent(name, properties, value)` |
| `eski sağlayıcı.ConsentRequired = true` + `eski sağlayıcı.ConsentGiven = true` | `o.RequiresPrivacyConsent = true` + `Pushizo.SetConsentGiven(true)` |
| `eski sağlayıcı.Debug.LogLevel = …` | `Pushizo.Debug.SetLogLevel(LogLevel.Debug)` |
| — | `Pushizo.Inbox.ListAsync / MarkReadAsync / MarkAllReadAsync / ArchiveAsync / UnreadCountAsync`, `Pushizo.User.GetPreferencesAsync / SetPreferencesAsync` |
Ayrıntı: [.NET MAUI SDK](/docs/sdk/maui).
### 5.10 Unity
| eski sağlayıcı Unity SDK 5 | `com.pushizo.unity` |
|-----------------------|------------------------|
| eski sağlayıcı UPM paketleri, `using eski sağlayıcıSDK;` | Git URL ile UPM paketi (`https://github.com/pushizo/pushizo-unity.git#0.1.0`), `using PushizoSdk;`; Project Settings → **Pushizo** (App ID, App Group, `google-services.json` yolu) |
| `eski sağlayıcı.Initialize("APP_ID")` | `Pushizo.Initialize("app_…", new PushizoOptions { ApiUrl = "https://api.pushizo.com" })` |
| `eski sağlayıcı.Login("id")` / `eski sağlayıcı.Logout()` | `Pushizo.Login("id", identityHash)` / `Pushizo.Logout()` |
| `eski sağlayıcı.User.AddTag / AddTags / RemoveTag / RemoveTags`, `AddAlias / AddAliases / RemoveAlias`, `AddEmail / RemoveEmail / AddSms / RemoveSms` | aynı adlar + `GetTags()`, `SetLanguage("tr")` |
| `eski sağlayıcı.User.PushSubscription.OptIn() / OptOut() / Id / Token / OptedIn` + `Changed` | aynı adlar |
| `await eski sağlayıcı.Notifications.RequestPermissionAsync(true)` | `await Pushizo.Notifications.RequestPermissionAsync(fallbackToSettings: true)` |
| `eski sağlayıcı.Notifications.Clicked / ForegroundWillDisplay / PermissionChanged += (sender, e) => …` | aynı olay adları, `Action<T>` imzası: `Clicked += e => …`, `PermissionChanged += granted => …` (olaylar ana iş parçacığında gelir) |
| `eski sağlayıcı.Notifications.ClearAllNotifications()` | `Pushizo.Notifications.ClearAll()` |
| `eski sağlayıcı.InAppMessages.AddTrigger / AddTriggers / RemoveTrigger / RemoveTriggers / ClearTriggers / Paused` + `Clicked / WillDisplay / DidDisplay / WillDismiss / DidDismiss` | aynı adlar + `GetTriggers()`, `SetScreen(name)` |
| `eski sağlayıcı.Session.AddOutcome / AddUniqueOutcome / AddOutcomeWithValue` | `Pushizo.Session.SendEvent(name, properties, value)` |
| `eski sağlayıcı.ConsentRequired = true` + `eski sağlayıcı.ConsentGiven = true` | `new PushizoOptions { RequiresPrivacyConsent = true }` + `Pushizo.SetConsentGiven(true)` |
| `eski sağlayıcı.Debug.LogLevel = …` | `Pushizo.Debug.SetLogLevel(LogLevel.Debug)` |
| — | `Pushizo.Inbox.*Async`, `Pushizo.User.GetPreferencesAsync / SetPreferencesAsync` |
Android bağımlılıkları EDM4U ile çözülür ve `Assets/google-services.json` derlemede işlenir (Firebase Unity SDK'sı gerekmez); iOS'ta Push yeteneği, NSE hedefi ve Podfile derleme sonrası işlemle eklenir. Ayrıntı: [Unity SDK](/docs/sdk/unity).
### 5.11 Bildirim kutusu ve tercihler (eski sağlayıcıda olmayanlar)
- **Bildirim kutusu:** `add_to_inbox: true` ile gönderilen mesajlar kullanıcıya özel kutuda listelenir (`Inbox.list`, `markRead`, `markAllRead`, `archive`, `unreadCount`); eski sağlayıcıda karşılığı olmadığından taşınacak veri yoktur.
- **Kategori tercihleri:** eski sağlayıcı "push categories" slidedown'ı yerine dashboard'da **Kategoriler** tanımlanır; SDK'lar `User.getPreferences()` / `setPreferences([...])` ile kullanıcı × kanal opt-in yazar. eski sağlayıcıda tag ile yönettiğiniz tercihleri (`tags.promo_optin = "0"`) göç sonrasında bir kerelik `PUT /v1/apps/{app_id}/users/by/external_id/{id}/preferences` çağrısıyla gerçek tercihe çevirebilirsiniz.
## 6. Kesinti planı (çift yazma dönemi)
1. **Hafta 0 — hazırlık:** kanallar doğrulanmış, player göçü tamamlanmış, sunucu entegrasyonu Pushizo'ya da yazıyor (çift yazma: her bildirim/olay eski sağlayıcı ve Pushizo'ya birlikte gider; Pushizo'da `test_devices_only: true` ile yalnızca test cihazlarına gönderim yapın ya da hedefi küçük bir segmentle sınırlayın).
2. **Hafta 1–2 — istemci yayını:** Pushizo SDK'sını içeren mobil sürümü yayınlayın, web'de SDK'yı canlıya alın. Her iki SDK aynı uygulamada çalışabilir; ancak **push izni istemini yalnızca Pushizo'dan** yapın (iki SDK birden izin isterse kullanıcı kafa karışıklığı yaşar) ve Android'de yalnızca birinin ön plan bildirimi göstermesine izin verin (`addForegroundLifecycleListener` + `preventDefault()`).
3. **Hafta 2–3 — trafik kaydırma:** işlemsel bildirimleri (sipariş, OTP) Pushizo'dan, kampanyaları dashboard'dan göndermeye başlayın. eski sağlayıcıdan gönderimi segment bazında azaltın; **Analitik → Teslimat raporu** ve **Sağlayıcı sağlığı** sayfalarından teslim/açılma oranlarını karşılaştırın.
4. **Hafta 3–4 — kesinti:** eski sağlayıcı gönderimlerini durdurun, `migrate:legacy` ile son bir fark çekimi yapın (içe aktarma idempotenttir; yeni player'lar eklenir, mevcutlar güncellenir), eski sağlayıcı webhook/Event Stream'lerini kapatın, REST API anahtarını iptal edin.
5. **Eski uygulama sürümleri:** eski sağlayıcı SDK'sı içeren eski sürümler hâlâ eski sağlayıcıya token kaydeder; bu kullanıcılar için haftalık fark çekimini sürümün kullanım payı %1'in altına inene kadar sürdürün.
6. **Geri alma:** çift yazma döneminde eski sağlayıcı yapılandırmasını silmeyin; kritik bir sorun olursa gönderimi eski sağlayıcıya geri alabilirsiniz. Kesintiden sonra `.p8`/servis hesabı anahtarlarını eski sağlayıcıdan kaldırın.
## 7. Sık karşılaşılan farklar
| Konu | eski sağlayıcı | Pushizo |
|------|-----------|------------|
| Kimlik | `eski kimlik` + `external_id` | `hs_id` + `external_id` + özel alias'lar (`users/by/{label}/{value}`) |
| Cihaz birleştirme | `login` cihazı kullanıcıya bağlar | Aynı; anonim kullanıcı birleştirilir, `user.merged` webhook'u üretilir |
| Hata biçimi | `{ "errors": [...] }` | `{ "error": { "code": "HS_…", … } }`, `x-request-id` |
| Hız sınırı | sabit | Plan bazlı (`api_rps`), `X-RateLimit-*` başlıkları, `429 HS_RATE_LIMITED` + `Retry-After` |
| Sessiz saatler / frekans sınırı | panel | App ayarları; mesaj başına `quiet_hours_exempt`, `frequency_cap_exempt` |
| Şablon dili | `{{ tag }}` basit değişkenler | Liquid (`{{ user.tags.name }}`, filtreler, koşullar) |
| Web push kimliği | eski sağlayıcı VAPID | Kendi VAPID çiftiniz (kanal kaydında üretilir); abonelikler yeniden oluşur |
| Veri yerleşimi | eski sağlayıcı bulutu (birincil veri merkezi AB) | Bölge seçimi (EU/TR hücreleri, [veri yerleşimi](/docs/security/data-residency)) ya da kendi altyapınız (self-host) |Runbook: Gerçek cihaza APNs ve FCM test gönderimi
Bu runbook, kanal kimlik bilgilerinin gerçek sağlayıcılarla çalıştığını doğrulamak için izlenir. Faz 3'te "doğrula" ucu sağlayıcıya test bağlantısı kurar; gerçek cihaza gönderim Faz 4 (POST /v1/notifications) ve Faz 5…
Firebase Cloud Messaging'den (FCM) Pushizo'ya göç
Bildirimleri doğrudan Firebase Cloud Messaging (FCM HTTP v1 API, Firebase Admin SDK ya da Firebase konsolu) ile gönderiyorsanız bu rehber Pushizo'ya geçişi anlatır. Pushizo Android'de FCM'i taşıma katmanı olarak…