Subscribe to Webhooks

Receive an HTTP call to your server whenever a document or template changes status, is edited, or another event occurs.

Overview

A Webhook is an HTTP-based callback from the PandaDoc API to your server. Create a webhook subscription to receive instant updates whenever the status of your documents changes or other events occur.

📘

Testing Webhook locally

We only support SSL (https://) connections, which may present challenges when testing webhooks locally.
A possible solution is to use tools like ngrok, localtunnel, or any other service that can proxy requests to your local address and provide a globally accessible HTTPS URL.

Limits

You can create up to 300 webhook subscriptions per workspace.

Webhook data is provided in the form of an array and may contain multiple notification objects simultaneously.

Document webhooks always return basic information along with recipients. You can configure your webhook to also include fields, tokens, products, and pricing.

📘

Working with multiple workspaces

Webhook subscriptions are per workspace. With a given subscription, you'll receive events only from the workspace to which that subscription belongs.

To receive events from multiple workspaces, create a separate subscription for each workspace you want to track. Consider using workspace_id as a path parameter in your URL to easily distinguish events from different workspaces.

Webhook Setup

There are two ways to set up a new webhook:

  1. Using Developer Dashboard UI
  2. Using Create Webhooks Subscription API endpoint

Use cases

  • Creating a document in PandaDoc is an asynchronous operation. You can either choose to poll the Document Status to get the status update or have PandaDoc push document information to your application when a document's state changes.

Events

Event NameEvent Description
document_state_changedThe document's status has changed. Use this event to track when a document moves to draft (indicating that its asynchronous creation is complete), or when it is viewed, completed, paid, etc.
document_completed_pdf_readyThe document has been completed, and a PDF has been generated and saved to our e-vault. Use this event to download the PDF of the completed document via the Download Protected Document endpoint.
document_updatedThe document has been returned to draft status.
recipient_completedA recipient has completed the document. JSON payload additionally contains action_by and action_date fields, providing information on who completed the document and when. This event does not indicate that the document itself is completed.
document_deletedThe document has been removed.
document_creation_failedDocument creation via API has failed. This event is triggered when the document does not move to "draft" status.
document_section_addedIndicates that the asynchronous creation of a section is complete.
quote_updatedA user clicked the "Save" button after updating a quote in the Quote Builder within PandaDoc.
template_createdA new template has been created.
template_updatedAn existing template has been updated. This includes changes to the name, roles, or the addition of sections..
template_deletedThe template has been deleted.
content_library_item_createdA new CLI has been created.
content_library_item_creation_failedCLI creation via API has failed.

Webhook Payload Examples

Most webhook payloads follow the example below:

  1. The event that triggered the webhook

  2. Document data in the format similar to the one Document Details returns, depending on the fields you have selected when setting up the webhook:

    Creation of a webhook subscription with additional fields

    Creation of a webhook subscription with additional fields

Here's the webhook example:

[
    {
        "event": "document_state_changed",
        "data":
        {
            "id": "eHCjisfzWydzJnbqnBbvAj",
            "name": "My document for webhooks testing",
            "date_created": "2024-03-18T15:55:03.090372Z",
            "date_modified": "2024-03-18T16:26:46.286951Z",
            "expiration_date": "2024-05-17T16:26:45.583270Z",
            "autonumbering_sequence_name": null,
            "created_by":
            {
                "id": "uHfzrB4Goai39ZkxDRLpMo",
                "email": "[email protected]",
                "first_name": "Test",
                "last_name": "Test",
                "avatar": null,
                "membership_id": "vk5aRcJG4RTd2J83wNGFT5"
            },
            "metadata":
            {},
            "tokens":
            [],
            "fields":
            [],
            "products":
            [],
            "pricing":
            {
                "tables":
                [],
                "quotes":
                [],
                        "merge_rules":
                        []
                    }
                ],
                "total": "130"
            },
            "tags":
            [],
            "status": "document.completed",
            "recipients":
            [],
            "sent_by":
            {
                "id": "uHfzrB4Goai39ZkxDRLpMo",
                "email": "[email protected]",
                "first_name": "Test",
                "last_name": "Test",
                "avatar": null,
                "membership_id": "vk5aRcJG4RTd2J83wNGFT5"
            },
            "grand_total":
            {
                "amount": "130.00",
                "currency": "USD"
            },
            "template":
            {
                "id": "A9VkDBdjqn3HvRx7zyME3n",
                "name": "My template for webhooks testing"
            },
            "version": "2",
            "linked_objects":
            []
        }
    }
]

document_creation_failed payload example

When PandaDoc fails to process your upload and create a document, the webhook payload looks like this:

[
  {
    "event": "document_creation_failed",
    "data": {
      "id": "ptSNky6J4Q8yDh3QKwa7fZ",
      "error": {
        "type": "validation_error",
        "detail": "Role for form field with name='userName' is not provided in payload"
      }
    }
  }
]

It includes the error type and error message.

Debugging and Retries

You can use the Webhooks History tab to see all the requests PandaDoc made to your server, and response statuses.

2748

Webhook History

2288

Webhook manual retries

PandaDoc makes 3 attempts to deliver a webhook. After this, you can manually retry failed webhooks, as shown in the picture.

Timeouts

  • A connection timeout occurs after 5 seconds if our server can't connect to the URL you put into the subscription.
  • A read timeout occurs after 20 seconds.

Subscription deactivation

PandaDoc deactivates your webhook subscription in any of these cases:

  1. Your server responds with the 410 error.
  2. HTTP 4xx client errors and/or HTTP 5xx server errors occur for a period of 7 days, without any successful responses.

Webhooks Verification

When making a PandaDoc API request, your identity is confirmed by the access_token submitted in each request header. To make sure requests are valid and not a request spoofed as PandaDoc in your own application, we support webhook verification.

Using your shared key and a signature, you can verify the contents of a webhook are authentic and un-tampered.

Your shared key can be found in the Developer Dashboard part of your account settings.

🚧

Protect Your Shared Key

Treat your shared key like a password to prevent others from generating a webhook with a payload that could pass a signature verification test.

Webhooks are signed with a signature generated by taking an HMAC-SHA256 hash of the webhook post’s raw HTTP Body (UTF8 encoding). This signature is sent with the Webhook post as a query parameter in your URL by using the signature variable.

https://yourdomain.com/webhook-handler/?signature={signature}
signature = hmac.new(str(shared_key), str(request_body), digestmod=hashlib.sha256).hexdigest()
signature = hash_hmac('sha256', $request_body, $shared_key);
public static bool VerifyHMACSHA256(string sharedKey, string source, string dest) {
    byte[] key = Encoding.UTF8.GetBytes(sharedKey);
    using (HMACSHA256 hmac256 = new HMACSHA256(key))
    {
         var hashedSource = hmac256.ComputeHash(Encoding.UTF8.GetBytes(source));
                   
         if (hashedSource == null || hashedSource.Length == 0)
         {
             return false;
         }

         var hmac = new StringBuilder();
         for (int i = 0; i < hashedSource.Length; i++)
         {
             // the format hex string should always be 2 characters
             hmac.AppendFormat("{0:x2}", hashedSource[i]);
         }

         return hmac.ToString().Equals(dest);
    }
}
const hmac = crypto.createHmac('sha256', sharedKey);

hmac.update(requestBody);

const signature = hmac.digest('hex');

IP safelist

Below is a whitelist of IPs from where you can safely accept PandaDoc webhooks events:

US servers

  • 52.12.31.116/32
  • 52.37.240.175/32
  • 35.167.41.246/32

EU servers

If you only work with http://app.pandadoc.eu/, whitelist the IPs below:

  • 3.76.175.239
  • 18.192.96.161
  • 18.158.79.41