Back to Play 5 Resources
Play 5: Client Onboarding

E-Signature Webhook Setup Guide (PandaDoc)

Same for PandaDoc.

E-Signature Webhook Setup Guide (PandaDoc)

PandaDoc webhooks

eliminate manual status checks and data entry after clients sign engagement letters. When a document gets signed, PandaDoc sends a POST request to your endpoint. Your system receives it, updates the CRM
, triggers the welcome sequence, and provisions access - all without human intervention.

This guide covers the complete setup: creating the webhook

in PandaDoc, building a secure endpoint, handling the payload, and testing the integration.

What You Need Before Starting

PandaDoc Account with API Access
Business or Enterprise plans include webhook

functionality. Standard plans do not. Check your plan at Settings > Billing. If you're on Standard, upgrade or use Zapier as a workaround (covered below).

Webhook

Receiver
You need a publicly accessible HTTPS endpoint that accepts POST requests. Three options:

  • Zapier: No coding required. $20/month for the Starter plan gets you webhook
    triggers.
  • Make (formerly Integromat): Similar to Zapier. Free tier includes webhooks
    .
  • Custom endpoint: Your own server or serverless function (AWS Lambda, Azure Functions, Cloudflare Workers).

System to Update
Identify where signed document data should flow: Salesforce, HubSpot, Clio, practice management system, or a database. You'll need API

credentials for that system.

Step 1: Create the Webhook
in PandaDoc

  1. Log into PandaDoc and go to Settings > Integrations > Webhooks
    .
  2. Click Create Webhook
    .
  3. Enter a name: "Client Onboarding - Document Signed".
  4. Paste your webhook
    URL. For Zapier, you'll get this in Step 2. For custom endpoints, use your server URL (must start with https://).
  5. Select events to monitor:
    • document_state_changed: Fires when status changes (sent, viewed, completed, voided).
    • recipient_completed: Fires when a specific recipient finishes signing.
    • document_completed: Fires when all recipients sign.

For client onboarding, use document_completed. This ensures you only trigger downstream actions after everyone signs.

  1. Under Authentication, choose Shared Secret.
  2. Generate a random 32-character string (use a password manager or openssl rand -hex 16). Save this - you'll use it to verify webhook
    authenticity.
  3. Paste the secret into the Shared Secret field.
  4. Click Save.

Do not click "Test Webhook

" yet. Your endpoint doesn't exist.

Step 2: Build Your Webhook
Endpoint

Option A: Zapier (No-Code)

  1. In Zapier, create a new Zap.
  2. For the trigger, search for Webhooks
    by Zapier
    and select Catch Hook.
  3. Copy the webhook
    URL Zapier provides.
  4. Go back to PandaDoc and edit your webhook
    . Paste the Zapier URL into the Webhook
    URL
    field. Save.
  5. Return to Zapier and click Test Trigger. In PandaDoc, send a test document to yourself and complete it. Zapier should catch the payload.
  6. Add an action step. Search for your CRM
    (Salesforce, HubSpot, etc.) and select Update Record or Create Record.
  7. Map fields:
    • data.name → Document Title
    • data.recipients[0].email → Contact Email
    • data.status → Status Field
    • data.id → PandaDoc Document ID (store this for reference)
  8. Add a second action: Gmail or SendGrid to send a welcome email.
  9. Test the Zap end-to-end. Turn it on.

Zapier Limitation: You cannot verify the shared secret in Zapier's free webhook

trigger. Upgrade to a paid plan and use Code by Zapier to add signature verification (see custom endpoint code below for logic).

Option B: Custom Endpoint (Node.js Example)

This example uses Express.js and runs on any Node server or AWS Lambda.

Install dependencies:

npm install express body-parser crypto

Create webhook-handler.js:

const express = require('express');
const bodyParser = require('body-parser');
const crypto = require('crypto');

const app = express();
app.use(bodyParser.json());

const PANDADOC_SECRET = 'your_32_char_secret_here';
const PORT = process.env.PORT || 3000;

// Verify PandaDoc signature
function verifySignature(payload, signature) {
  const hash = crypto
    .createHmac('sha256', PANDADOC_SECRET)
    .update(JSON.stringify(payload))
    .digest('hex');
  return hash === signature;
}

app.post('/webhooks/pandadoc', async (req, res) => {
  const signature = req.headers['x-pandadoc-signature'];
  
  if (!signature || !verifySignature(req.body, signature)) {
    console.error('Invalid signature');
    return res.status(401).send('Unauthorized');
  }

  const event = req.body;
  const eventType = event.event;
  const document = event.data;

  console.log(`Received event: ${eventType} for document ${document.id}`);

  if (eventType === 'document_completed') {
    const recipientEmail = document.recipients[0].email;
    const documentName = document.name;

    // Update CRM
    await updateCRM(recipientEmail, {
      engagement_letter_signed: true,
      pandadoc_document_id: document.id,
      signed_date: new Date().toISOString()
    });

    // Send welcome email
    await sendWelcomeEmail(recipientEmail, documentName);

    // Provision client portal access
    await provisionPortalAccess(recipientEmail);
  }

  res.status(200).send('OK');
});

async function updateCRM(email, data) {
  // Replace with your CRM API call
  // Example: Salesforce REST API, HubSpot API, etc.
  console.log(`Updating CRM for ${email}:`, data);
}

async function sendWelcomeEmail(email, documentName) {
  // Replace with your email service
  // Example: SendGrid, AWS SES, Postmark
  console.log(`Sending welcome email to ${email}`);
}

async function provisionPortalAccess(email) {
  // Replace with your portal provisioning logic
  console.log(`Provisioning portal access for ${email}`);
}

app.listen(PORT, () => {
  console.log(`Webhook server running on port ${PORT}`);
});

Deploy this code:

  • AWS Lambda: Use the Serverless Framework or AWS SAM. Add API
    Gateway to expose the endpoint.
  • Azure Functions: Use the Azure Functions Core Tools.
  • Heroku: Push to a Git repo and deploy.
  • Your own server: Run with node webhook-handler.js behind Nginx with SSL.

Critical: Your endpoint must use HTTPS. PandaDoc rejects HTTP URLs. Use Let's Encrypt for free SSL certificates or deploy to a platform that provides HTTPS by default.

Step 3: Configure Webhook
Security

PandaDoc includes an x-pandadoc-signature header in every webhook

request. This is an HMAC-SHA256 hash of the payload using your shared secret.

Verification logic (already in the code above):

const hash = crypto
  .createHmac('sha256', PANDADOC_SECRET)
  .update(JSON.stringify(req.body))
  .digest('hex');
return hash === signature;

If the signature doesn't match, reject the request with a 401 status. This prevents attackers from sending fake webhook

payloads to your endpoint.

Additional security measures:

  • Whitelist PandaDoc's IP addresses in your firewall (contact PandaDoc support for the current list).
  • Rate-limit the endpoint to 100 requests per minute.
  • Log all webhook
    attempts (successful and failed) for audit purposes.

Step 4: Handle Webhook
Payload

PandaDoc sends a JSON payload. Here's a sample document_completed event:

{
  "event": "document_completed",
  "data": {
    "id": "AbCdEfGh123456",
    "name": "Engagement Letter - Smith & Associates",
    "status": "document.completed",
    "date_created": "2025-01-15T10:30:00Z",
    "date_completed": "2025-01-15T14:22:00Z",
    "recipients": [
      {
        "email": "john.smith@smithassociates.com",
        "first_name": "John",
        "last_name": "Smith",
        "has_completed": true
      }
    ],
    "metadata": {
      "client_id": "12345",
      "matter_type": "Tax Preparation"
    }
  }
}

Key fields to extract:

  • data.id: PandaDoc document ID (store this in your CRM
    for reference).
  • data.name: Document title.
  • data.recipients[0].email: Signer's email (use this to look up the contact in your CRM
    ).
  • data.metadata: Custom fields you added when creating the document. Use these to pass client ID, matter type, or other identifiers.

Handling multiple recipients:

If your engagement letter requires two signatures (e.g., managing partner and client), loop through data.recipients and check has_completed for each. Only proceed when all recipients have has_completed: true.

Step 5: Test the Integration

Test 1: Send a Test Webhook

from PandaDoc

  1. In PandaDoc, go to Settings > Integrations > Webhooks
    .
  2. Click the three dots next to your webhook
    and select Send Test Event.
  3. Choose document_completed.
  4. Check your endpoint logs. You should see the test payload arrive.

Test 2: Complete a Real Document

  1. Create a new document in PandaDoc using a test template.
  2. Send it to your own email address.
  3. Open the email and sign the document.
  4. Within 30 seconds, check your endpoint logs. You should see the document_completed event.
  5. Verify that your CRM
    updated correctly and the welcome email sent.

Test 3: Simulate a Signature Mismatch

  1. Modify your endpoint code to use a wrong secret.
  2. Send another test webhook
    from PandaDoc.
  3. Confirm your endpoint rejects it with a 401 status.
  4. Restore the correct secret.

Test 4: Check PandaDoc's Webhook

Logs

  1. In PandaDoc, go to Settings > Integrations > Webhooks
    .
  2. Click your webhook
    name.
  3. View the Recent Deliveries tab.
  4. Confirm all deliveries show a 200 status. If you see 4xx or 5xx errors, click the delivery to see the response body and debug.

Troubleshooting Common Issues

Webhook

not firing:
Check that you selected the correct event (document_completed, not document_state_changed). Verify your PandaDoc plan includes webhooks
.

401 Unauthorized errors:
Your signature verification is failing. Print the expected hash and the received signature to compare. Ensure you're hashing the raw request body, not a parsed object.

Duplicate events:
PandaDoc may retry webhooks

if your endpoint doesn't respond within 10 seconds. Make your endpoint idempotent: check if the document ID already exists in your CRM
before updating.

Webhook

delays:
PandaDoc typically delivers webhooks
within 5-10 seconds. If you see delays over 1 minute, check PandaDoc's status page or contact support.

Next Steps

Once your webhook

is stable, extend it:

  • Add a document_viewed event to log when clients open the engagement letter (useful for follow-up).
  • Store the signed PDF in your document management system (use PandaDoc's API
    to download the file).
  • Trigger a Slack notification to the engagement team when a high-value client signs.
  • Create a dashboard that shows signing velocity (time from send to completion).

PandaDoc webhooks

turn document signing from a manual checkpoint into an automated trigger. Build this once, and every signed engagement letter becomes the starting gun for your onboarding process.

Revenue Institute

Reviewed by Revenue Institute

This guide is actively maintained and reviewed by the implementation experts at Revenue Institute. As the creators of The AI Workforce Playbook, we test and deploy these exact frameworks for professional services firms scaling without new headcount.

Revenue Institute

Need help turning this guide into reality? Revenue Institute builds and implements the AI workforce for professional services firms.

RevenueInstitute.com