Logo
Apdf tutorials July 2026 6 min read

How to Get Notified When Someone Opens Your PDF

You send the proposal. Then: silence. Did they open it? Did it land in spam? Are they reading it right now, while you're busy doing something else? Email attachments can't answer any of that — once the PDF leaves your outbox, you're blind.

In this tutorial you'll fix that: share your PDF through a tracking link and get a notification the moment it's opened — with the reader's name attached.

What you'll build
A live notification pipeline for your PDF: your recipient opens the document, Apdf fires a document:loaded event at your webhook within seconds — and a tiny forwarder (or a Zap) turns it into a Slack message: 📄 Alex Rivera (alex@acme.example) just opened “Proposal Acme Redesign”
Free plan for tracking · automations need Pro (14-day trial, no card)
1

Put your PDF behind a tracking link

Upload your PDF in the Apdf dashboard — or do it via the API. Either way, your file becomes a document: hosted in a viewer that streams back everything readers do.

curl -X POST https://apdf.io/api/docs \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -d file="https://your-server.com/proposal.pdf" \
  -d name="Proposal Acme Redesign"
Tip: First document? Your workspace needs a handle first — it becomes part of your viewer URLs (docs.apdf.io/your-handle/…). The dashboard asks for one before your first upload; API users can set it in the account settings.

The response contains your document ID and its viewer URL:

{
    "data": {
        "doc_id": "5018b-c0414-f11fc",
        "viewer_url": "https://docs.apdf.io/studio/5018b-c0414-f11fc",
        "file_url": "https://apdf-files.s3.eu-central-1.amazonaws.com/docs/202607/72ae50876a5f6132b7962.pdf",
        "name": "Proposal Acme Redesign",
        "file_size": 38347,
        "file_pages": 3,
        "is_public": false,
        "created_at": "2026-07-21T12:08:19.000000Z",
        "archived_at": null
    }
}
2

Create a link for your recipient

Don't share the document URL directly — mint a recipient link instead. Each recipient gets their own tokenized URL, so every event that comes back already carries their name. No logins for the reader, no guesswork for you.

curl -X POST https://apdf.io/api/docs/5018b-c0414-f11fc/links \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -d name="Alex Rivera" \
  -d email="alex@acme.example"
{
    "data": {
        "token": "H7rC2Uu8",
        "name": "Alex Rivera",
        "email": "alex@acme.example",
        "url": "https://docs.apdf.io/studio/5018b-c0414-f11fc?v=H7rC2Uu8",
        "is_active": true,
        "last_viewed_at": null,
        "created_at": "2026-07-21T12:08:32.000000Z"
    }
}

That url is what you send to Alex — by email, chat, wherever. You can also create links in the dashboard with two clicks.

3

Point an automation at your webhook

Now tell Apdf what to do when the document is opened. Automations follow a simple when→then rule: when an event fires, then POST the details to your webhook URL.

  1. In the dashboard, go to Documents → Automations and click New Automation.
  2. Under “When this happens…” pick Document Opened as the trigger. Scope it to all documents or just this one.
  3. Skip the conditions — you want every open. (Later you can filter: only re-opens, only reads over a minute, only page 4…)
  4. Under “Then do this…” paste your Webhook URL and save. The automation is live immediately.
Tip: No webhook endpoint yet? Grab a free one at webhook.site to see the events flowing within seconds — then swap in your real endpoint (step 5) when you're ready.
Plan note: Automations are a Pro feature. Every new account can trial Pro for 14 days — no credit card — which is plenty to build everything in this tutorial.
4

Open the link and watch the event arrive

That's the whole setup. Open the recipient link in your browser to play the reader — within a couple of seconds, your webhook receives the event:

{
    "automation": {
        "id": "11387-cf58d-123af",
        "name": "Ping me when the proposal is opened"
    },
    "document": {
        "doc_id": "5018b-c0414-f11fc",
        "name": "Proposal Acme Redesign",
        "viewer_url": "https://docs.apdf.io/studio/5018b-c0414-f11fc",
        "file_pages": 3
    },
    "trigger": {
        "matched_event": "document:loaded",
        "conditions_evaluated": false
    },
    "event": {
        "session_id": "kQ3xYb9pL2mNv8dF",
        "viewer_id": "vXw4tR7cJ9hZs2aE",
        "recipient": {
            "name": "Alex Rivera",
            "email": "alex@acme.example"
        },
        "data": null
    },
    "session": {
        "total_duration_ms": 0,
        "pages_viewed": 0,
        "completion_rate": 0
    },
    "timestamp": "2026-07-21T12:23:37+00:00"
}

Note the event.recipient block: because Alex opened his link, the event arrives with his name — no lookup tables, no joins. This payload is your raw material; the last step turns it into a notification.

5

Turn the event into a Slack message

Apdf delivers the signal; where it goes next is your call. Two ways to make it a Slack notification — pick your lane:

  1. In Zapier, create a Zap with the trigger Webhooks by Zapier → Catch Hook and copy the generated hook URL.
  2. Paste that URL as the webhook in your Apdf automation (step 3) and open your recipient link once — Zapier now knows the payload shape.
  3. Add the action Slack → Send Channel Message and compose it from the payload fields: Event Recipient Name opened Document Name.
  4. Turn the Zap on. The same recipe works for Gmail, Teams, or a CRM update instead of Slack.
Tip: Rather skip the catch hook? The official Apdf app for Zapier has a trigger for every reader event and creates the automation itself when you turn the Zap on.

A complete forwarder in ~20 lines — no dependencies, Node 18+. It receives the Apdf event and posts a message to a Slack incoming webhook.

const http = require('node:http');

const SLACK_WEBHOOK_URL = process.env.SLACK_WEBHOOK_URL;

http.createServer(async (req, res) => {
    let body = '';
    for await (const chunk of req) body += chunk;

    const { document, event } = JSON.parse(body);
    const who = event.recipient
        ? `${event.recipient.name} (${event.recipient.email})`
        : 'Someone';

    await fetch(SLACK_WEBHOOK_URL, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            text: `📄 ${who} just opened "${document.name}" — ${document.viewer_url}`,
        }),
    });

    res.writeHead(200).end('ok');
}).listen(4783, () => console.log('Listening on http://localhost:4783'));
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/XXX/YYY/ZZZ node notify.js

Deploy it anywhere that gives you a public URL, paste that URL into your automation, and every open lands in your channel:

📄 Alex Rivera (alex@acme.example) just opened “Proposal Acme Redesign”

Where to go from here

The open is just the first signal — the same pipeline carries every reader event.

Ready to see who reads?