Electronic Signature on Your Website: PandaDoc Integration
Consider this: when a customer visits your site, places an order, and must sign a contract—each step must be seamless. PandaDoc allows embedding the signature directly into the interface, but the API does not forgive mistakes: wrong signature field, incorrect status, missed webhook—and the document stalls. Recently, a company approached us where the signing process took up to 3 days due to manual PDF exchange. By implementing PandaDoc, we reduced the cycle to 15 minutes—an 80x acceleration.
However, the complexity of integration is often underestimated. Even with ready-made SDKs, issues arise with status handling, API limits, and webhook security. We have accumulated experience from over 30 successful implementations and know how to avoid typical pitfalls.
How PandaDoc Solves the E-signature Problem on Your Site
PandaDoc provides a REST API for the full document lifecycle. Key capabilities: creating documents from templates or PDFs, sending for signature, embedding the signing session (embedded signing), handling webhook notifications, and downloading completed documents. Embedded signing increases conversion by 30–40% compared to redirecting to an external service—this is confirmed by our projects.
Typical Mistakes in PandaDoc Integration
The most common mistake is improper handling of document statuses. For example, attempting to send a document for signature before it is fully uploaded (status document.uploaded). PandaDoc returns a 400 error. The second most frequent is ignoring API rate limits (429 Too Many Requests). Without retry logic with exponential backoff, the integration fails under peak load. The third is skipping HMAC-SHA256 verification in webhooks: failing to verify the signature risks accepting forged events. In every project, we include protection against these scenarios.
How to Set Up a Webhook for PandaDoc
Webhooks are key for real-time document status tracking. In the PandaDoc Developer Dashboard: specify your handler URL, select events (e.g., document_state_changed). PandaDoc sends a POST request with a JSON array of events. Always verify the HMAC-SHA256 signature from the x-pandadoc-signature header—otherwise you are vulnerable to event spoofing. We implement a handler with guaranteed delivery and retries on failures.
Embedded Signing: How It Works
Embedded signing allows the client to sign the document without leaving your site. The signing session opens in an iframe; PandaDoc notifies completion via postMessage. This gives you full control over UX—you don’t lose the user to an external service. We use this method by default.
App Registration and Authentication
In the PandaDoc Developer Dashboard: create an app → get Client ID and Client Secret. Two authentication modes:
- API Key — a simple key in the header, for server-side integrations without user context.
- OAuth 2.0 — for multi-user applications.
// Simplest option for your own site
$headers = [
'Authorization' => 'API-Key ' . config('services.pandadoc.api_key'),
'Content-Type' => 'application/json',
];
For OAuth — standard Authorization Code Flow at app.pandadoc.com/oauth2/authorize. Details in the official documentation.
Creating Documents from Template or PDF
| Method | Advantages | Disadvantages |
|---|---|---|
| From template | Autofill data, branding, fewer errors | Requires a pre-created template |
| From PDF | Flexibility, any document | Manual signature field placement |
Using a template is optimal for standard contracts. Creation steps:
- Get the template ID from PandaDoc.
- Prepare an array of recipients and tokens.
- Call the API to create the document.
- Wait for the
document.uploadedstatus. - Send the document for signature.
class PandaDocService
{
private string $baseUrl = 'https://api.pandadoc.com/public/v1';
public function createFromTemplate(
string $templateId,
array $recipient,
array $tokens
): array {
$response = Http::withHeaders([
'Authorization' => 'API-Key ' . config('services.pandadoc.api_key'),
'Content-Type' => 'application/json',
])->post("{$this->baseUrl}/documents", [
'name' => "Contract — {$recipient['email']}",
'template' => ['id' => $templateId],
'recipients' => [
[
'email' => $recipient['email'],
'first_name' => $recipient['first_name'],
'last_name' => $recipient['last_name'],
'role' => 'client',
],
],
'tokens' => array_map(fn($k, $v) => ['name' => $k, 'value' => $v],
array_keys($tokens), $tokens),
'metadata' => [
'order_id' => $recipient['order_id'] ?? '',
],
]);
return $response->json();
}
}
Tokens are variables in the template like [COMPANY_NAME], [CONTRACT_DATE].
From PDF — when the document is already generated:
public function createFromPDF(string $pdfPath, array $recipient): array
{
// Step 1: upload file
$uploadResponse = Http::withHeaders([
'Authorization' => 'API-Key ' . config('services.pandadoc.api_key'),
])->attach('file', file_get_contents($pdfPath), 'contract.pdf')
->post("{$this->baseUrl}/documents");
$documentId = $uploadResponse->json('id');
// Step 2: wait for document processing (usually a few seconds)
$this->waitForStatus($documentId, 'document.uploaded');
// Step 3: add signature field
Http::withHeaders([
'Authorization' => 'API-Key ' . config('services.pandadoc.api_key'),
'Content-Type' => 'application/json',
])->patch("{$this->baseUrl}/documents/{$documentId}", [
'recipients' => [[
'email' => $recipient['email'],
'role' => 'Signer',
]],
'fields' => [[
'field_id' => 'sig1',
'type' => 'signature',
'role' => 'Signer',
'page' => 0,
'x' => 100,
'y' => 600,
'width' => 200,
'height' => 50,
]],
]);
return ['id' => $documentId];
}
private function waitForStatus(string $documentId, string $status): void
{
$attempts = 0;
do {
sleep(1);
$doc = Http::withHeaders([
'Authorization' => 'API-Key ' . config('services.pandadoc.api_key'),
])->get("{$this->baseUrl}/documents/{$documentId}")->json();
$attempts++;
} while ($doc['status'] !== $status && $attempts < 15);
}
Sending and Embedded Signing
public function sendDocument(string $documentId, string $message = ''): void
{
Http::withHeaders([
'Authorization' => 'API-Key ' . config('services.pandadoc.api_key'),
'Content-Type' => 'application/json',
])->post("{$this->baseUrl}/documents/{$documentId}/send", [
'message' => $message ?: 'Please review and sign the document.',
'subject' => 'Document for signing',
'silent' => false,
]);
}
public function getSessionLink(string $documentId, string $recipientEmail): string
{
$response = Http::withHeaders([
'Authorization' => 'API-Key ' . config('services.pandadoc.api_key'),
'Content-Type' => 'application/json',
])->post("{$this->baseUrl}/documents/{$documentId}/session", [
'recipient' => $recipientEmail,
'lifetime' => 3600,
]);
return $response->json('id');
// URL for iframe: https://app.pandadoc.com/s/{session_id}
}
Webhook and Download
public function handlePandaDocWebhook(Request $request): Response
{
$signature = $request->header('x-pandadoc-signature');
$body = $request->getContent();
$expected = hash_hmac('sha256', $body, config('services.pandadoc.webhook_key'));
if (!hash_equals($expected, $signature)) {
abort(403);
}
foreach ($request->json() as $event) {
if ($event['event'] === 'document_state_changed'
&& $event['data']['status'] === 'document.completed') {
$docId = $event['data']['id'];
DownloadPandaDocJob::dispatch($docId);
}
}
return response()->noContent();
}
PandaDoc may send multiple events in one webhook request—iterate the array. Downloading a completed document is implemented via GET /documents/{id}/download.
Handling PandaDoc API Errors
Typical errors: rate limit exceeded (429), invalid document status, timeouts when creating from PDF. We use retry logic with exponential backoff and log every failure. For critical operations, we set up monitoring—99% of requests pass without errors. Also consider the API limit: PandaDoc allows up to 10 requests per second for the Business plan, so design the integration accordingly.
Process and Timeline
| Stage | Duration |
|---|---|
| Analytics (CRM, scenarios) | 1 day |
| Design (authentication, structure) | 0.5 day |
| Implementation (code, tests) | 1–2 days |
| Testing (scenarios, webhooks) | 0.5 day |
| Deployment and monitoring | 0.5 day |
Total: basic integration — from 2 to 3 working days, with embedded signing and approval workflow — 4–5 days.
If you want to implement e-signature on your site, contact us for a preliminary assessment.
What’s Included
- App registration in PandaDoc (API Key or OAuth)
- Integration for creating documents from templates/PDFs
- Setup of embedded signing on your site
- Webhook notification handling with signature verification
- Implementation of completed document download
- Unit tests and integration tests
- API and administration documentation
- Monitoring and alerting for critical failures
- 30-day post-deployment warranty
Checklist of typical tasks
- Register app in PandaDoc
- Choose authentication method (API Key / OAuth)
- Create document templates (optional)
- Implement document creation from template/PDF
- Embed signing session (embedded signing)
- Handle webhooks with signature verification
- Download completed documents
- Write unit tests and integration tests
- Set up monitoring and alerting
We have been working with PandaDoc for over 5 years, completed more than 30 integrations for various companies. We provide a warranty on functionality after deployment. Order PandaDoc integration and speed up document workflow. We will assess your project for free and offer the optimal solution.







