Back to Blog
PHPPDF SigningAPIDevelopers

How to Sign PDF Documents in PHP (With Real Code)

PDFSignify TeamApril 12, 202611 min read

Signing PDFs in PHP is a common requirement for applications that handle contracts, invoices, compliance documents, or legal agreements. The challenge is that PHP has no built-in support for applying cryptographic digital signatures to PDF files. This guide shows you how to do it properly using the PDFSignify API — a synchronous REST API that signs PDFs with real digital certificates and returns the result immediately.

Why Add PDF Signing to a PHP Application?

Many business applications are built with PHP — CRMs, ERPs, admin panels, invoicing systems, HR portals, and SaaS platforms. Adding digital signature capability means your users can sign documents without leaving your application, and the signatures are cryptographically verifiable.

  • Automate contract and approval workflows without manual print-scan cycles
  • Produce documents with signatures that are verifiable in Adobe Acrobat and other PDF readers
  • Embed document signing directly into your existing PHP backend
  • Meet compliance requirements that demand tamper-evident digital signatures

The Architecture: How It Works

The PDFSignify API is intentionally simple. It is a synchronous, stateless API. Your PHP application sends a request containing the PDF, the signing certificate (.pfx or .p12), and the certificate password. The API returns the signed PDF as binary data in the response body. There is no document storage, no queues, no webhooks, and no polling.

  • Your PHP app sends a multipart/form-data POST request to the sign-pdf endpoint
  • The request includes the PDF file, the certificate file, and the certificate password
  • The API applies the cryptographic signature and returns the signed PDF immediately
  • Your app saves the signed PDF or streams it to the user

This means your PHP application stays in complete control. There is no external state to manage and no callbacks to handle.

Authentication

The API uses two custom headers for authentication: AccessKey and SecretKey. You get these from your PDFSignify dashboard. Include them on every request. There is no OAuth, no Bearer token, and no session management.

php
<?php
$headers = [
    'AccessKey: ' . getenv('PDFSIGNIFY_ACCESS_KEY'),
    'SecretKey: ' . getenv('PDFSIGNIFY_SECRET_KEY'),
];

Step 1: Basic PDF Signing

Here is the simplest way to sign a PDF. This example uses PHP's CURLFile class to handle the multipart upload cleanly:

php
<?php
$ch = curl_init('https://api.pdfsignify.com/api/v1/sign-pdf');

curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        'AccessKey: YOUR_ACCESS_KEY',
        'SecretKey: YOUR_SECRET_KEY',
    ],
    CURLOPT_POSTFIELDS => [
        'certificate' => new CURLFile('certificate.pfx', 'application/x-pkcs12'),
        'certificatePassword' => 'your_certificate_password',
        'pdf' => new CURLFile('contract.pdf', 'application/pdf'),
    ],
]);

$response = curl_exec($ch);

if ($response === false) {
    die('cURL Error: ' . curl_error($ch));
}

$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
curl_close($ch);

if (strpos($contentType, 'application/pdf') !== false) {
    file_put_contents('signed_contract.pdf', $response);
    echo 'Contract signed successfully.';
} else {
    echo 'Signing failed: ' . $response;
}

That is all you need for a basic integration. The API returns the signed PDF as the response body with a Content-Type of application/pdf. If something goes wrong, you get an error message instead.

Step 2: Customize the Signature Appearance

The API supports extensive signature customization. You can control the position, size, message text, date format, and even add a background image like a company logo. When using many optional fields, building the multipart body manually gives you full control:

php
<?php
$cert = file_get_contents('certificate.pfx');
$pdf = file_get_contents('contract.pdf');
$logo = file_get_contents('company_logo.png');

$boundary = uniqid();
$delimiter = '--------------------' . $boundary;
$data = [];

$data[] = '--' . $delimiter;
$data[] = 'Content-Disposition: form-data; name="certificate"; filename="certificate.pfx"';
$data[] = 'Content-Type: application/x-pkcs12';
$data[] = '';
$data[] = $cert;

$data[] = '--' . $delimiter;
$data[] = 'Content-Disposition: form-data; name="certificatePassword"';
$data[] = '';
$data[] = 'your_certificate_password';

$data[] = '--' . $delimiter;
$data[] = 'Content-Disposition: form-data; name="pdf"; filename="contract.pdf"';
$data[] = 'Content-Type: application/pdf';
$data[] = '';
$data[] = $pdf;

$data[] = '--' . $delimiter;
$data[] = 'Content-Disposition: form-data; name="signatureBackgroundImage"; filename="company_logo.png"';
$data[] = 'Content-Type: image/png';
$data[] = '';
$data[] = $logo;

$fields = [
    'signaturePageAppearance' => '-1',
    'timezone' => 'UTC',
    'signatureMessage' => 'Digitally signed by Your Company',
    'signatureDateFormat' => 'Y-m-d H:i:s',
    'signatureHeight' => '100',
    'signatureWidth' => '200',
    'signatureYPosition' => '50',
    'signatureXPosition' => '350',
    'pdfMetadataAuthor' => 'Your Company',
    'pdfMetadataTitle' => 'Signed Contract',
];

foreach ($fields as $key => $value) {
    $data[] = '--' . $delimiter;
    $data[] = 'Content-Disposition: form-data; name="' . $key . '"';
    $data[] = '';
    $data[] = $value;
}

$data[] = '--' . $delimiter . '--';
$data[] = '';
$postData = implode("\r\n", $data);

$ch = curl_init('https://api.pdfsignify.com/api/v1/sign-pdf');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: multipart/form-data; boundary=' . $delimiter,
    'AccessKey: YOUR_ACCESS_KEY',
    'SecretKey: YOUR_SECRET_KEY',
]);

$response = curl_exec($ch);

if ($response === false) {
    echo 'cURL Error: ' . curl_error($ch);
} else {
    $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
    if (strpos($contentType, 'application/pdf') !== false) {
        file_put_contents('signed_contract.pdf', $response);
        echo 'Signed PDF with custom appearance saved.';
    } else {
        echo 'Error: ' . $response;
    }
}

curl_close($ch);

Step 3: Validate the Certificate First

If your application accepts certificates uploaded by users, validate them before attempting to sign. The check-certificate-password endpoint tells you immediately whether the certificate and password are correct:

php
<?php
function isCertificateValid(string $certPath, string $password): bool
{
    $ch = curl_init('https://api.pdfsignify.com/api/v1/check-certificate-password');

    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_HTTPHEADER => [
            'AccessKey: ' . getenv('PDFSIGNIFY_ACCESS_KEY'),
            'SecretKey: ' . getenv('PDFSIGNIFY_SECRET_KEY'),
        ],
        CURLOPT_POSTFIELDS => [
            'certificate' => new CURLFile($certPath, 'application/x-pkcs12'),
            'certificatePassword' => $password,
        ],
    ]);

    $response = curl_exec($ch);
    curl_close($ch);

    $result = json_decode($response, true);
    return $result['success'] ?? false;
}

if (isCertificateValid('user_cert.pfx', 'user_password')) {
    echo 'Certificate is valid. Ready to sign.';
} else {
    echo 'Invalid certificate or wrong password.';
}

Understanding the Signature Options

Here is what each optional field controls:

  • signatureBackgroundImage — an image file (PNG, JPG) used as the background of the visible signature block
  • signaturePageAppearance — which page to display the visible signature on. Set to -1 to show it on every page.
  • signatureMessage — text displayed inside the signature, e.g., 'Digitally signed by John Smith'
  • signatureDateLabel — label text before the date (leave empty for no label)
  • signatureDateFormat — PHP date format string for the timestamp, e.g., 'Y-m-d H:i:s'
  • signatureHeight and signatureWidth — dimensions of the visible signature area in PDF points
  • signatureXPosition and signatureYPosition — where to place the signature on the page
  • timezone — timezone string for the signing timestamp, e.g., 'UTC', 'America/New_York', 'Europe/Berlin'

Use Cases for PHP Teams

PHP is widely used in business applications where document signing is a natural fit. Here are the most common use cases:

  • Invoicing systems — automatically sign invoices before sending them to clients
  • Contract management — sign contracts as part of a CRM or deal-closing workflow
  • HR onboarding — sign offer letters and employment agreements
  • Compliance workflows — apply tamper-evident signatures to audit documents
  • ERP and admin panels — add signing to internal approval processes
  • Legal document platforms — let users sign documents with their own certificates

Production Best Practices

  • Store your AccessKey and SecretKey in environment variables, not in source code
  • Always check the HTTP status code and Content-Type before processing the response
  • Set a reasonable CURLOPT_TIMEOUT for large PDF files
  • Log errors with enough detail to debug, but never log the SecretKey or certificate password
  • Keep certificate files outside your web root so they cannot be accessed directly via URL
  • Handle the binary PDF response correctly — do not json_decode it

Why PDFSignify Is Different

Most eSignature APIs require you to upload a document, create a signing request, add signers by email, wait for them to sign through a web interface, and then poll or listen for webhooks to download the result. PDFSignify works differently. It is a synchronous signing API — you provide the PDF and certificate, and you get the signed document back in the same HTTP response. There are no stored documents, no email flows, and no external signer portals.

This makes it ideal for backend automation where your application controls the signing workflow entirely.

Send the PDF. Send the certificate. Get the signed document. That is the entire workflow.