Ihre Abonnenten zu SubSovereign migrieren
Sie wechseln von RevenueCat, Adapty, Qonversion oder Ihrem eigenen Backend? Ihre bestehenden Abonnenten kommen mit. Dieser Leitfaden behandelt den Import selbst und — ebenso wichtig — die Umstellung, denn dort liegt das eigentliche Risiko.
Lesen Sie dies zuerst. Der Import ist sicher und wiederholbar. Die Umstellung ist der Moment, der Planung erfordert, denn solange Sie Ihre Store-Benachrichtigungen nicht auf uns umgeleitet haben, werden Verlängerungen weiterhin an Ihren bisherigen Anbieter zugestellt. Planen Sie §4, bevor Sie §2 ausführen.
Die zwei Hälften einer Migration — lesen Sie dies, bevor Sie eine planen
Jede Migration besteht aus zwei Mechanismen, und sie auseinanderzuhalten ist der Unterschied zwischen einem reibungslosen Wechsel und einem Versprechen, das Sie nicht halten können.
Die clientseitige Neuvalidierung ist die GARANTIE. Sobald Ihre App unser SDK ausliefert, sendet das Gerät jedes Abonnenten beim nächsten Öffnen seinen aktuellen Beleg erneut. Die Kennung stammt von Apple oder Google selbst — aktuell, korrekt und vollständig. Das funktioniert für jeden Store, benötigt von niemandem einen Export und kann keine veraltete Kennung transportieren. Jeder Abonnent, der Ihre App öffnet, wird konstruktionsbedingt korrekt migriert.
Der Import ist der BESCHLEUNIGER. Er existiert, damit Ihre Abonnenten nicht ausgesperrt werden, bevor sie die App das nächste Mal öffnen, und damit Ihre Auswertungen am ersten Tag nicht leer sind. Er kopiert, was Ihr bisheriger Anbieter wusste.
Warum beides: Abonnenten öffnen Apps in sehr unterschiedlichen Abständen. Wer Ihre App sechs Wochen lang nicht startet, würde mit reiner Neuvalidierung sechs Wochen lang als nicht berechtigt erscheinen. Der Import gibt ihm ab der ersten Minute Kontinuität; die Neuvalidierung macht sie verbindlich.
🚩 Wenn Sie nur eines behalten können, behalten Sie die Neuvalidierung. Ein Import, der für einen Store keine zuordenbare Kennung liefern kann (siehe unten), ist für diese Abonnenten keine Migration — er ist ein Datensatz, der still abläuft.
Was Ihr bisheriger Anbieter tatsächlich liefern kann
Der Import benötigt originalTransactionId, und was Ihr Anbieter herausgibt, unterscheidet sich je nach Store. Prüfen Sie Ihren, bevor Sie darauf aufbauen:
| Anbieter | Apple | |
|---|---|---|
| RevenueCat | ✅ Der Datenexport enthält original_transaction_id. |
⚠️ Der Export liefert eine Order ID, die nicht das Purchase Token ist — eine andere Kennung, auf die eine Verlängerungsbenachrichtigung nicht passt. Ihre v2-API ist schlechter: Sie gibt nur die neueste Transaktions-ID preis, die sich bei jeder Verlängerung ändert. |
| Adapty / Qonversion | Prüfen Sie den Export auf Apples Original Transaction ID, bevor Sie planen. | Prüfen Sie, ob Sie das Purchase Token oder eine Bestell-/Transaktionsreferenz erhalten. Bestätigen Sie es, nehmen Sie es nicht an. |
| Superwall | ⚠️ Vor der Planung klären. Sie betreiben eine eigene Schicht für Berechtigungen, Käufe und Belegprüfung — die Kennung existiert dort also. Offen ist, ob der Export Ihnen Apples original_transaction_id gibt oder nur Superwalls eigene Abonnement-Referenz. |
⚠️ Dieselbe Frage, dieselbe Antwort: Klären Sie schriftlich, ob Sie das Purchase Token oder eine Bestell-/Transaktionsreferenz erhalten, bevor Sie darauf aufbauen. |
| Ihr eigenes Backend | Was Sie beim Kauf gespeichert haben — meist das Richtige. | Sie halten das Purchase Token höchstwahrscheinlich bereits, weil Sie es zum Validieren brauchten. |
| Stripe | entfällt | entfällt — wir lesen Ihr Stripe-Konto direkt, kein Export nötig. Siehe §3b. |
🚩 Ein Wechsel von RevenueCat importiert also für Apple sauber und kann Google-Abonnenten nicht mit einer zuordenbaren Kennung importieren. Das lässt sich nicht wegprogrammieren: Die Kennung, die wir benötigen, ist in den Daten, die Sie erhalten können, schlicht nicht enthalten. Diese Abonnenten kommen über die clientseitige Neuvalidierung herüber — liefern Sie das SDK aus, leiten Sie die Benachrichtigungen um (§4), und jeder von ihnen wird beim nächsten Öffnen der App korrekt migriert. Importieren Sie die Apple-Seite für die Kontinuität und lassen Sie Google sich selbst heilen.
1. Was Sie brauchen
- Ihren Abonnenten-Export, mit einer Zeile pro aktiver Berechtigung (siehe Format unten).
- Eine Produktzuordnung. Jede Produkt-ID in Ihrem Export muss zuerst einer Ihrer Zugriffsstufen zugeordnet werden — Dashboard → Ihre App → Produkte. 🚩 Der Import wird nicht raten, welche Stufe er gewähren soll. Ein nicht zugeordnetes Produkt wird abgelehnt und gemeldet, denn Raten wäre ein Weg an genau dem Schutz vorbei, der verhindert, dass ein Kunde eine Stufe beansprucht, die er nicht gekauft hat.
- Einen Administrator-Zugang für die Organisation, der die App gehört. Betrachter (Viewer) können nicht importieren: Der Import gewährt bezahlten Zugriff in großem Umfang.
2. Das Format
Ein JSON-Objekt pro Abonnentenberechtigung:
{
"userId": "user_12345",
"email": "person@example.com",
"store": "apple",
"productId": "pro_monthly",
"originalTransactionId": "1000000987654321",
"expiresAt": "2027-01-01T00:00:00Z",
"willRenew": true,
"status": "active"
}
| Feld | Pflicht | Hinweise |
|---|---|---|
userId |
ja | Ihre eigene Nutzerkennung — dieselbe, die Ihre App an unser SDK übergibt. |
store |
ja | apple · google · amazon · roku · stripe |
productId |
ja | Muss in Ihrer Produktzuordnung vorhanden sein, sonst wird die Zeile abgelehnt. |
originalTransactionId |
ja | 🚩 Siehe unten. Apples Original Transaction ID, Googles Purchase Token oder die Abonnement-ID des Anbieters. |
status |
ja | active · trialing · grace_period · expired · lifetime · cancelled · refunded · billing_failed |
expiresAt |
nein | ISO 8601. Bei lifetime weglassen. |
amazonUserId |
bei Amazon | 🚩 Amazons eigene Nutzerkennung aus deren SDK — nicht Ihre App-Nutzerkennung. Amazon schlägt einen Beleg über (diese Kennung, receiptId) nach; ohne sie könnte die Berechtigung nie erneut geprüft werden. Amazon-Zeilen ohne sie werden abgelehnt. |
email, willRenew |
nein |
🚩 originalTransactionId ist das Feld, das über den Erfolg entscheidet
Es ist nicht nur ein Schlüssel für uns — es ist der Weg, auf dem eine Verlängerung den Abonnenten Monate später wiederfindet. Wenn Apple oder Google eine Serverbenachrichtigung für eine Verlängerung sendet, wird genau über diese Kennung zugeordnet.
Eine ohne sie importierte Zeile erzeugt die schlimmste Art von Fehler: Am ersten Tag sieht alles korrekt aus, und dann verliert der Abonnent bei seiner nächsten Verlängerung still den Zugriff, ohne dass irgendwo ein Fehler gemeldet wird.
Deshalb lehnen wir solche Zeilen ab, statt sie anzunehmen. Ohne sie zu importieren ist keine Abkürzung, sondern ein verzögerter Ausfall.
🚩 Nicht jeder Anbieter kann Ihnen dieses Feld für jeden Store liefern — siehe Tabelle oben. Wo Ihr Export es tatsächlich nicht liefern kann (RevenueCat + Google ist der bekannte Fall), umgehen Sie das nicht, indem Sie einen Wert erfinden: Jeder Platzhalter erzeugt genau den hier beschriebenen stillen Verlängerungsfehler. Importieren Sie die Stores, für die Sie echte Kennungen haben, und lassen Sie die clientseitige Neuvalidierung den Rest tragen.
3. Ausführen — immer zuerst ein Probelauf
Dieser Abschnitt beschreibt den CSV-/Export-Weg für Apple, Google, Amazon und Roku. Für Stripe springen Sie zu §3b — wir lesen Ihr Konto direkt, und es ist nichts zu bauen.
Am einfachsten geht es im Dashboard: Subscriber Migration (Insights → Customers) → From an export. Fügen Sie den Export als CSV oder JSON ein oder laden Sie die Datei. Die Seite prüft die Zeilen zuerst und schreibt nichts, bis Sie es verlangen; sie zerlegt große Dateien für Sie in Stapel; und sie listet jede abgelehnte Zeile mit Zeilennummer auf, damit Sie sie in der Datei korrigieren können, die Sie tatsächlich haben. Die API unten ist dasselbe, falls Sie lieber skripten.
🚩 Diese Zeilen sind eine Behauptung, keine Bestätigung. Ein Export ist das, was Ihr bisheriger Anbieter Ihnen übergeben hat; importierte Zeilen landen daher unbestätigt und reihen sich in die Prüfwarteschlange (§6) ein, wo wir sie anschließend gegen den echten Store abgleichen. Direkt aus Stripe gelesene Zeilen (§3b) sind anders — sie kommen bereits bestätigt an.
Der Probelauf ist die Voreinstellung. Sie müssen ausdrücklich verlangen, dass etwas geschrieben wird.
curl -X POST https://api.miimagineai.com/api/v1/apps/YOUR_APP_ID/import/subscribers \
-H "Authorization: Bearer $DASHBOARD_TOKEN" -H 'Content-Type: application/json' \
-d '{ "source": "revenuecat", "rows": [ … bis zu 1000 Zeilen … ] }'
Sie erhalten einen Bericht zurück:
{
"dryRun": true, "received": 1000, "accepted": 987, "newUsers": 964,
"missingTransactionIds": 6,
"unmappedProducts": ["legacy_annual_v1"],
"rejected": [ { "index": 12, "code": "unmapped_product", "reason": "…" } ]
}
Lesen Sie diese drei Zahlen, bevor Sie festschreiben:
missingTransactionIds— Zeilen, die sich ins Leere verlängern würden. Korrigieren Sie den Export, fahren Sie nicht fort.unmappedProducts— ordnen Sie sie zu und führen Sie es erneut aus.newUsers— wie viele Abonnenten dabei angelegt werden. Wirkt die Zahl falsch, ist Ihr FelduserIdvermutlich die falsche Kennung.
Wenn der Bericht sauber ist, ergänzen Sie "dryRun": false und senden ihn erneut.
Stapel und Wiederaufnahme
Senden Sie höchstens 1000 Zeilen pro Anfrage; eine große Migration besteht aus vielen Stapeln. Der Import ist idempotent — dasselbe Stapelpaket erneut zu senden aktualisiert, statt zu duplizieren, sodass ein Skript, das auf halbem Weg abbricht, einfach von vorn laufen kann. Eine fehlerhafte Zeile wird gemeldet und übersprungen; sie verwirft nie die guten Zeilen daneben.
Was abgelehnt wird, und warum
| Code | Bedeutung |
|---|---|
missing_transaction_id |
Siehe oben. Der wichtige Fall. |
unmapped_product |
Ordnen Sie das Produkt zuerst zu; wir raten keine Stufe. |
transaction_owned_by_another_user |
Diese Store-Transaktion gehört in dieser App bereits zu einem anderen Konto. Eine Transaktion, ein Eigentümer — meist ein zusammengeführtes Konto oder fehlerhafte Daten im Export. |
invalid_store / invalid_status / invalid_expiry |
Fehlerhafter Wert. |
3b. Stripe — wir lesen Ihr Konto direkt
Stripe ist der einzige Store, der „wer sind alle meine Abonnenten?" beantworten kann. Apple, Google, Amazon und Roku beantworten nur „ist dieser Beleg noch gültig?", einen Beleg nach dem anderen. Für Stripe gibt es daher keinen Export anzufordern und keine CSV zu bauen: Subscriber Migration im Dashboard (Insights → Customers) durchläuft Ihr Konto und importiert es.
Bevor Sie beginnen, müssen zwei Dinge vorhanden sein, und die Seite sagt Ihnen, wenn sie es nicht sind:
- Ihr geheimer Stripe-Schlüssel, hinterlegt unter Apps → store credentials. Es gibt keinen Rückfall auf die Plattform und wird nie einen geben — ohne Ihren Schlüssel schlägt der Import geschlossen fehl, statt fremde Daten zu lesen.
- Ihre Produkte den Stufen zugeordnet. Ordnen Sie entweder den Price (
price_…) oder das Product (prod_…) zu; beides funktioniert. Nicht zugeordnete Produkte werden abgelehnt statt erraten — das ist korrektes Verhalten, kein Fehler.
Sagen Sie uns dann, wo die Nutzerkennung Ihrer App in Stripe steht — Abonnement-Metadaten, Kunden-Metadaten oder die Stripe-Kundenkennung selbst. 🚩 Es gibt keine Voreinstellung und wir raten nicht. Stripe kennt den Kunden als cus_…; nur Sie wissen, welcher Ihrer Nutzer das ist, und ein Fehlgriff scheitert nicht laut — er gewährt den bezahlten Zugriff eines Abonnenten dem Konto einer anderen Person.
Zuerst die Vorschau. Die Vorschau ändert nichts und zeigt Ihnen genau, was geschehen würde: wie viele Abonnements gelesen wurden, wie viele importiert würden und jedes einzelne, das abgelehnt würde — mit der Begründung in verständlicher Sprache. Lesen Sie die Ablehnungen, bevor Sie importieren: Sie sind die Liste der zu behebenden Dinge, kein Rauschen.
Große Konten werden automatisch über mehrere Anfragen durchlaufen; die Seite drosselt sich selbst, um innerhalb der Ratenbegrenzung zu bleiben, und zeigt den Fortschritt an.
Was abgelehnt wird, und warum das die richtigen Antworten sind
| Code | Bedeutung |
|---|---|
never_paid |
Das Abonnement ist incomplete — es kam nie zu einer ersten Zahlung. Es zu importieren würde jemandem bezahlten Zugriff gewähren, der nie bezahlt hat. |
access_expired |
Der Status gewährt Zugriff, aber der Abrechnungszeitraum ist bereits abgelaufen. Zugriff wird hier über den Status entschieden, ein Import würde also unbefristeten Zugriff gewähren. |
ambiguous_product |
Positionen eines Abonnements verweisen auf zwei verschiedene Stufen. Wir lehnen ab, statt zu wählen — die höhere zu nehmen wäre ein Stufenaufstieg auf dem Migrationsweg, und die erste zu nehmen wäre willkürlich. |
no_user_id |
Keine App-Nutzerkennung dort, wo wir laut Ihrer Angabe nachsehen sollten. Siehe oben: Das ist die gefährliche Rateaufgabe. |
unmapped_product |
Ordnen Sie das Produkt zu und führen Sie es erneut aus. |
🚩 Gesehene, aber nicht zugeordnete Produkte werden auch dann gemeldet, wenn sie nichts blockiert haben. Heute blockieren sie nichts, aber sobald Sie eines zuordnen, wird jedes Abonnement, das es neben einem anderen zugeordneten Produkt trägt, mehrdeutig und wird künftig abgelehnt. Besser jetzt erfahren als beim zweiten Durchlauf.
Warum Stripe-Zeilen bereits bestätigt ankommen
Eine importierte Zeile gilt normalerweise als Behauptung, bis wir sie beim Store bestätigen (§6). Stripe-Zeilen sind anders: Sie wurden über Stripes eigene API mit Ihren Zugangsdaten gelesen und landen daher bereits store-bestätigt. Das ist eine Aussage über die Herkunft, nicht über Stripe — jeder künftige Zahlungsdienstleister, den wir direkt auslesen, verhält sich genauso, und jede von einem Menschen gelieferte Zeile bleibt eine Behauptung, gleich welchen Store sie nennt.
Lieber die API? Die Seite ist ein Client für POST /apps/:appId/import/stripe; dieselben Optionen sind dryRun, userIdSource, userIdKey und startingAfter für die Seitenweiterschaltung.
4. 🚩 Die Umstellung — planen Sie sie, bevor Sie importieren
Der Import kopiert einen Zustand. Er leitet nicht die Zukunft um. Solange Sie die Server-Benachrichtigungen Ihrer Stores nicht auf SubSovereign umgestellt haben, gehen Verlängerungen, Kündigungen und Erstattungen weiterhin an Ihren alten Anbieter.
Die empfohlene Reihenfolge:
- Importieren (dieser Leitfaden). Ihre Berechtigungen existieren nun in beiden Systemen.
- Parallel betreiben. Lassen Sie den alten Anbieter aktiv und vergleichen Sie. Noch hat sich nichts verschoben.
- Einen App-Build mit unserem SDK ausliefern. Berechtigungen werden von uns aufgelöst, und weil Sie dieselben Transaktionskennungen importiert haben, behalten bestehende Abonnenten ihren Zugriff ohne eigenes Zutun.
- Store-Benachrichtigungen umleiten auf unsere Webhook-Endpunkte (Apple App Store Server Notifications, Google Real-Time Developer Notifications). Das ist der eigentliche Umschaltmoment.
- Den Import erneut ausführen für alles, was sich im Zeitfenster geändert hat. Er ist idempotent — genau dafür ist dieser Schritt da.
- Den alten Anbieter abschalten, sobald ein vollständiger Verlängerungszyklus sauber durchgelaufen ist.
5. Was eine importierte Berechtigung ist — und was nicht
Eine importierte Berechtigung ist eine Gewährung bezahlten Zugriffs auf Aussage Ihres bisherigen Anbieters. Wir haben sie noch nicht selbst bei Apple oder Google geprüft.
Wir halten das ehrlich fest, statt es zu verbergen: Importierte Zeilen tragen imported_at und import_source, und ihr store_verified_at bleibt leer, bis die Berechtigung gegen den echten Store bestätigt wurde. „Welche davon haben wir auf Vertrauen gewährt?" ist damit jederzeit eine Frage mit einer exakten Antwort.
🚩 Das ist wichtig, falls eine Zahl je falsch aussieht. Importierte und bestätigte Berechtigungen sind nicht dieselbe Art von Tatsache, und eine Prüfung, die sie gleich behandelt, wäre irreführend.
6. Einen Import gegen den Store erneut prüfen
Nach dem Import können Sie uns jede Berechtigung gegen den echten Store prüfen lassen:
curl -X POST https://api.miimagineai.com/api/v1/apps/YOUR_APP_ID/import/verify \
-H "Authorization: Bearer $DASHBOARD_TOKEN" -H 'Content-Type: application/json' \
-d '{ "limit": 50 }'
Jede Zeile kommt mit einem von drei Ergebnissen zurück:
| Ergebnis | Bedeutung |
|---|---|
| verified | Der Store hat sie bestätigt. Wir übernehmen Ablaufdatum und Verlängerungskennzeichen des Stores anstelle der importierten — er ist die Autorität. |
| mismatch | Der Store hat dem Import widersprochen: kein aktives Abonnement oder ein aktives für ein anderes Produkt. |
| unverifiable | Wir konnten keine brauchbare Antwort erhalten — eine Ratenbegrenzung, ein Problem mit Zugangsdaten, ein vorübergehender Fehler. Das ist kein Widerspruch. |
🚩 Ein Mismatch entzieht niemals den Zugriff. An der Berechtigung ändert sich nichts; nur die Prüfspalten werden geschrieben, und die Abweichung wird einem Menschen zur Beurteilung vorgelegt. „Der Store hat es nicht bestätigt" und „dieser Abonnent ist nicht berechtigt" sind verschiedene Aussagen, und eine Store-API kann die erste aus Gründen zurückgeben, die nichts mit Ihrem Kunden zu tun haben. Auf dieser Grundlage sind wir nicht bereit, einen zahlenden Abonnenten zu kündigen — schon gar nicht einen, der gerade zu uns migriert ist. Siehe DECISIONS.md #70.
Führen Sie es in Stapeln aus (Standard 50, maximal 200) — Store-APIs sind ratenbegrenzt, und diese Aufrufe nutzen Ihre Store-Zugangsdaten. GET /apps/YOUR_APP_ID/import/status zeigt das aktuelle Bild:
{ "imported": 4820, "verified": 4776, "awaiting": 44, "mismatch": 11, "unverifiable": 33 }
Es gibt dafür bewusst noch keinen automatischen Zeitplan — wir rufen Ihre Store-Zugangsdaten lieber nicht unbeaufsichtigt auf einem Zeitgeber ab, den Sie nicht angefordert haben. Führen Sie es aus, wann es Ihnen passt; es ist idempotent und wiederaufnehmbar.