Logo

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”
About 10 minutes 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 DocumentsAutomations 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.

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?