Logo
Apdf tutorials September 2026 5 min read

How to Onboard Clients with a Tracked Welcome Packet and Intake Form

Client onboarding usually ships in three pieces: a welcome PDF nobody confirms reading, a form-tool link that feels like homework, and the follow-up email chasing both. Three tools, three URLs, and you still don't know if the packet was ever opened.

Collapse it into one document: the welcome packet with the intake form as its last page, behind a personal tracked link. The client reads, fills and submits in one visit — and every part of that reports back to you.

What you'll build
One link in the kickoff email, and everything you need back from it: Nadia read all 4 pages, answered the intake, prefers Slack — one session, fully attributed
Everything in this tutorial works on the free plan
1

Make the packet one document

Pages 1–3: the welcome content — how you work, the first 30 days, who to call. Page 4: a fillable intake form. Any PDF tool that creates form fields works; what matters is the field names, because they become the keys in every submission payload:

company_name         (text)
primary_contact      (text)
billing_email        (text)
current_tools        (text)
communication_pref   (radio: Email / Slack / Phone)

Then upload it privately:

curl -X POST https://apdf.io/api/docs \
  -H "Authorization: Bearer $API_TOKEN" -H "Accept: application/json" \
  --data-urlencode "file=https://your-site.example/onboarding-packet.pdf" \
  --data-urlencode "name=Welcome Packet — Northlight Onboarding" \
  -d "is_public=0"
Tip: Building the form page programmatically? The form submissions tutorial covers the field mechanics in depth — including the radio-button export-value trap when generating with pdf-lib.
2

One client, one link

curl -X POST https://apdf.io/api/docs/91fd5-09179-d072a/links \
  -H "Authorization: Bearer $API_TOKEN" -H "Accept: application/json" \
  --data-urlencode "name=Nadia Fontaine" \
  --data-urlencode "email=nadia@atelierfontaine.example"
{
    "data": {
        "token": "bsWyzJxs",
        "name": "Nadia Fontaine",
        "email": "nadia@atelierfontaine.example",
        "url": "https://docs.apdf.io/studio/91fd5-09179-d072a?v=bsWyzJxs",
        "is_active": true,
        "last_viewed_at": null,
        "created_at": "2026-07-23T09:31:23.000000Z"
    }
}

That URL is the entire kickoff email's call to action: “Everything you need is here — give it a read and fill in the last page.” No attachment, no separate form link, nothing to install on her side: the packet opens in the browser and the form is fillable right in the viewer.

3

The intake arrives — with a name on it

When Nadia hits Submit on page 4, the whole form lands in your workspace, attributed through her link:

GET /api/docs/91fd5-09179-d072a/form-submissions

{
    "doc_id": "91fd5-09179-d072a",
    "creator_id": "2CyFB2HjTsRn",
    "recipient": {
        "name": "Nadia Fontaine",
        "email": "nadia@atelierfontaine.example"
    },
    "fields": [
        { "id": "16R", "name": "company_name", "page": 4, "type": "text", "value": "Atelier Fontaine" },
        { "id": "20R", "name": "primary_contact", "page": 4, "type": "text", "value": "Nadia Fontaine" },
        { "id": "23R", "name": "billing_email", "page": 4, "type": "text", "value": "billing@atelierfontaine.example" },
        { "id": "26R", "name": "current_tools", "page": 4, "type": "text", "value": "Notion, Figma, QuickBooks" },
        { "id": "29R", "name": "communication_pref", "page": 4, "type": "radio", "value": false, "checked": false, "exportValue": "Email" },
        { "id": "34R", "name": "communication_pref", "page": 4, "type": "radio", "value": true, "checked": true, "exportValue": "Slack" },
        { "id": "39R", "name": "communication_pref", "page": 4, "type": "radio", "value": false, "checked": false, "exportValue": "Phone" }
    ],
    "created_at": "2026-07-23T09:34:33.000000Z",
    "updated_at": "2026-07-23T09:34:33.000000Z"
}

Everything the kickoff needs, machine-readable: the billing address for the first invoice, the tool stack for the integration checklist, and "exportValue": "Slack" telling you where she actually wants to talk. Prefer a push? An automation on the form-submit event delivers each intake to your webhook the moment it lands — the forms tutorial wires it.

4

And you know the packet was read

The same visit shows up in the document's sessions — proof the welcome content was read, not skipped on the way to the form:

{
    "session_id": "X5q9879PK1GehXdA",
    "viewer_id": "2CyFB2HjTsRn",
    "recipient": {
        "name": "Nadia Fontaine",
        "email": "nadia@atelierfontaine.example"
    },
    "device_type": "desktop",
    "browser": "Chrome",
    "os": "OS X",
    "referer": null,
    "utm_source": null,
    "created_at": "2026-07-23 09:33:55.864",
    "pages_viewed": 4,
    "total_duration_ms": 31061,
    "completion_pct": 100,
    "prints": 0,
    "downloads": 0
}

Same viewer_id as the submission — read and intake are one story. The clients who haven't submitted are visible too: opened-but-not-filled gets a gentle nudge, never-opened gets a resend. No more chasing people who already answered.

Where to go from here

The packet is live — these builds close the loop around it.

Ready to see who reads?