Documentation

Deeplinks

Deeplinks move a user from the shell into the exact page of your app with signed state. They are for browser navigation, not agent API calls.

Start With A Route Inventory

Before declaring deeplinks, list every navigable page in your app where a user lands with entity-specific or identity-specific state — an order ID, a document slug, a profile, a project ID. Then ask: could a conversation meaningfully navigate there? Only declare deeplinks for pages that answer yes.

A good deeplink inventory includes:

Entity detail pages
/orders/[orderId], /docs/[docId], /projects/[slug] — any page whose URL encodes a record ID
Filtered list views
/invoices?status=overdue, /tickets?assignee=me — deeplink state passes the filter params
Onboarding / wizard steps
/setup/billing, /invite/accept?token=... — if the shell can route users here
Not deeplinks
System routes, admin-only pages, and pages with no state

What A Deeplink Does

1

Duct chooses a route

The shell maps the user's intent to a manifest deeplink such as view_order.
2

Duct signs the state

The URL contains a short-lived token with the target path and required state.
3

Your receiver verifies it

Your app verifies the token server-side and redirects to the target page.
4

Your app confirms handoff

The receiver posts a callback so Duct knows navigation succeeded.

Declare A Deeplink

ts
export default defineDuctConfig({

Receiver Route

The generated receiver route verifies the token and redirects. Exact framework code varies, but the flow is the same.

ts
import { redirect } from 'next/navigation';

Plain HTTP Receiver

If you are using Python, Ruby, Go, or another backend, implement the same server-side steps: verify signature, validate expiry, apply state, redirect, then POST the callback.

Do not trust client state

Treat deeplink state as untrusted until the signed token is verified. Never accept arbitrary browser query params as authorization to access private records.