Migrating from Firebase Dynamic Links
Firebase Dynamic Links (FDL) shut down on August 25, 2025. Crisp is a drop-in resolver with the same link shape, deferred deep-link support, and one SDK call to swap. This guide takes you from a Firebase project to fully migrated in about 15 minutes.
page.link domain keeps working during the cutover.1. Install the SDK
Add the crisp SDK to your project. React Native and Flutter ship first (thin clients — hashing is server-side); native iOS and Android follow the same surface.
npm install @crisp-to/react-native @react-native-async-storage/async-storage
dependencies: crisp_flutter: ^0.1.0
.package(url: "https://github.com/crisp-to/crisp-ios", from: "0.1.0")
implementation("to.crisp:crisp-android:0.1.0")
npm install @crisp-to/web # web SDK — coming in phase 2
2. Map your domain & import
In the crisp dashboard, open Deep links → Firebase import, add your existing page.link domain, and upload the CSV you exported. Crisp remaps every short suffix, parameter, and fallback automatically.
3. Swap the resolver call
Replace your getDynamicLink / handleUniversalLink handler with the crisp deferred-link handler. One callback covers both live links and post-install deferred matches, so your routing logic stays.
// Before — Firebase Dynamic Links // dynamicLinks().onLink(link => { ... }) import { CrispSDK } from "@crisp-to/react-native"; CrispSDK.initialize({ apiKey: "sdk_live_…", appId: "app_…" }); CrispSDK.onDeferredLink((link) => { router.navigate(link.shortCode, link.params); // fires for live AND deferred });
// Before — FirebaseDynamicLinks.instance.onLink.listen(...) import 'package:crisp_flutter/crisp_flutter.dart'; CrispSDK.initialize(const CrispOptions(apiKey: 'sdk_live_…', appId: 'app_…')); CrispSDK.onDeferredLink((link) { router.go(link.shortCode!, extra: link.params); // live AND deferred });
// Before — DynamicLinks.dynamicLinks().handleUniversalLink(url) { ... } import CrispSDK CrispSDK.shared.initialize(apiKey: "sdk_live_…", appId: "app_…") CrispSDK.shared.onDeferredLink { link in Router.handle(shortCode: link.shortCode, params: link.params) }
// Before — FirebaseDynamicLinks.getInstance()... CrispSDK.initialize(context, "sdk_live_…", "app_…") CrispSDK.onDeferredLink { link -> navController.navigate(link.shortCode, link.params) }
// Web SDK ships in phase 2 — same resolver surface. const link = await crisp.resolve(window.location.href) router.push(link.params.path)
4. Deferred deep links
When a user taps a link without the app installed, crisp preserves the destination across the App Store / Play Store install. On first launch the SDK matches the install automatically — your onDeferredLink handler fires with link.deferred === true and the original params. No extra call needed.
Parameter reference
Every FDL query parameter maps to a crisp field. The import does this automatically; the table is for links you build by hand.
REST API
Everything the dashboard does — shorten, restyle, re-point — is available over HTTPS at api.crisp.to. Create a workspace key in Dashboard → API keys (it's shown once) and send it as a Bearer token. Keys work on every plan; plan limits apply to API calls exactly as they do in the dashboard.
curl https://api.crisp.to/v1/dashboard/workspaces/me \ -H "Authorization: Bearer wk_live_…"
Responses use one envelope: {"data": …} on success, {"error": {"code", "message"}} otherwise. Codes you'll see: invalid_key (401), not_found (404), validation_failed (400/409), plan_limit (402), rate_limited (429).
Short links API
Create a link — optional short_code (unique per domain), up to 10 tags, and an expires_at ISO timestamp (the link answers 410 after it):
curl -X POST https://api.crisp.to/v1/dashboard/links \ -H "Authorization: Bearer wk_live_…" -H "Content-Type: application/json" \ -d '{"type":"short","destination_url":"https://example.com/sale", "short_code":"sale","tags":["summer","email"]}' // 201 → {"data":{"link":{…},"url":"https://crisp.to/sale"}}
List and search with GET /v1/dashboard/links?type=short&q=…&tag=…, edit with PATCH /v1/dashboard/links/{id} (changing destination_url re-points the live short URL immediately), delete with DELETE, and export everything as CSV from GET /v1/dashboard/links/export. Free plans create 50 links per calendar month — a plan_limit error (402) tells you when you've hit it.
QR codes API
A QR code wraps its own short link, so every code is dynamic: re-point the destination any time and existing prints keep working. Scans are the wrapped link's clicks.
curl -X POST https://api.crisp.to/v1/dashboard/qr \ -H "Authorization: Bearer wk_live_…" -H "Content-Type: application/json" \ -d '{"name":"Menu QR","destination_url":"https://cafe.example/menu", "fg_color":"#1A1712","bg_color":"#FBF4E9"}' // 201 → {"data":{"qr":{…},"url":"https://crisp.to/x7Kq"}}
Colors (fg_color, bg_color, frame_color) work on every plan. The style object — module shapes (dots: square/dots/rounded), corner styles, error-correction level, and logo_url — is Creator and up (402 otherwise). GET /v1/dashboard/qr lists codes with scan counts; PATCH /v1/dashboard/qr/{id} restyles or re-points; DELETE removes the code and its wrapped link.
The machine-readable OpenAPI spec ships in the repo (apps/api-crisp-to/openapi.yaml) and covers the full surface, including bio pages and billing.