Back to Blog
PHPDigital CertificatePDF SigningSecurityAPI

Sign PDF with Certificate in PHP (Complete Guide + Real Examples)

PDFSignify TeamFebruary 25, 202614 min read

Digitally signing a PDF with a certificate in PHP means applying a cryptographic signature using a .pfx or .p12 file. This is not a visual stamp — it embeds verifiable proof of who signed the document and whether it has been tampered with. PHP alone cannot do this reliably, but with the PDFSignify API and cURL, you can sign PDFs with full certificate support in just a few lines of code.

Visual Signature vs. Digital Certificate Signature

Before diving into the code, it is important to understand the difference between these two approaches, because they solve very different problems:

  • Visual signature — an image or drawn signature placed on a PDF. It looks like a signature but provides no cryptographic verification.
  • Digital certificate signature — a cryptographic operation using a .pfx or .p12 certificate. It proves identity, ensures document integrity, and is verifiable by any PDF reader like Adobe Acrobat.

If your use case involves legal contracts, compliance, government documents, or any scenario where you need to prove the document has not been modified after signing, you need certificate-based digital signing.

Why PHP Cannot Do This Natively

PHP has OpenSSL bindings, but applying a cryptographic signature to a PDF is not just about encrypting bytes. It requires parsing the PDF structure, creating a signature dictionary, computing the hash over the correct byte ranges, and embedding the PKCS#7 signature object. Libraries like TCPDF and FPDI attempt this, but they are fragile, poorly maintained, and produce signatures that often fail validation in Adobe Acrobat.

The reliable approach is to delegate the signing operation to a purpose-built service. Your PHP code sends the PDF and certificate to the PDFSignify API, which performs the cryptographic signing and returns the signed PDF immediately.

How the PDFSignify API Works

The PDFSignify API is synchronous. You send a multipart/form-data request containing your PDF file, your .pfx/.p12 certificate, and the certificate password. The API signs the PDF and returns the signed document as binary data in the response. There are no webhooks, no document IDs, and no stored documents. You get the result immediately.

  • Endpoint: POST https://api.pdfsignify.com/api/v1/sign-pdf
  • Authentication: AccessKey and SecretKey headers (no Bearer tokens)
  • Input: PDF file + certificate file + certificate password
  • Output: Signed PDF returned directly as application/pdf

Prerequisites

  • PHP 7.4+ with the cURL extension enabled
  • A PDFSignify account with your AccessKey and SecretKey
  • A .pfx or .p12 digital certificate file and its password
  • A PDF document to sign

Quick Example Using CURLFile

If you just need a minimal working example, PHP's CURLFile class handles multipart uploads cleanly. This approach works well when you only need the required fields:

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('document.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_document.pdf', $response);
    echo 'Signed PDF saved successfully.';
} else {
    echo 'Error: ' . $response;
}

That is the simplest working implementation. The API receives your certificate and PDF, signs it, and returns the signed file. You save it to disk or stream it to the browser.

Full Example with All Options (Manual Multipart)

For full control over the signature appearance, metadata, and positioning, you need to build the multipart body manually. This is because CURLFile does not support mixing file uploads with many text fields cleanly in all PHP versions. Here is the complete example:

php
<?php
$cert = file_get_contents('certificate.pfx');
$pdf = file_get_contents('document.pdf');
$backgroundImage = file_get_contents('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="document.pdf"';
$data[] = 'Content-Type: application/pdf';
$data[] = '';
$data[] = $pdf;

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

$metadata = [
    'pdfMetadataAuthor' => 'Your Company',
    'pdfMetadataKeywords' => 'contract,signed,legal',
    'pdfMetadataTitle' => 'Signed Contract',
    'pdfMetadataSubject' => 'Client Agreement',
    'pdfMetadataCreator' => 'My PHP Application',
    'pdfMetadataProducer' => 'PDFSignify',
    'pdfMetadataCreationDate' => date('Y-m-d H:i:s'),
    'pdfMetadataModificationDate' => date('Y-m-d H:i:s'),
];
foreach ($metadata as $key => $value) {
    $data[] = '--' . $delimiter;
    $data[] = 'Content-Disposition: form-data; name="' . $key . '"';
    $data[] = '';
    $data[] = $value;
}

$signatureOptions = [
    'signaturePageAppearance' => '-1',
    'timezone' => 'UTC',
    'signatureMessage' => 'Digitally signed by Your Company',
    'signatureDateLabel' => '',
    'signatureDateFormat' => 'Y-m-d H:i:s',
    'signatureHeight' => '100',
    'signatureWidth' => '150',
    'signatureYPosition' => '100',
    'signatureXPosition' => '180',
];
foreach ($signatureOptions 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_document.pdf', $response);
        echo 'Signed PDF saved to signed_document.pdf';
    } else {
        echo 'Error: Expected PDF response, got ' . $contentType;
        echo "\nResponse: " . $response;
    }
}

curl_close($ch);

Understanding the Signature Options

The optional fields give you full control over how the signature appears on the PDF:

  • signaturePageAppearance — which page to show the visible signature on. Use -1 to show it on all pages.
  • signatureBackgroundImage — a custom image (like your company logo) displayed as the signature background.
  • signatureMessage — the text displayed in the signature block, such as 'Digitally signed by Your Company'.
  • signatureHeight, signatureWidth — dimensions of the visible signature area in PDF points.
  • signatureXPosition, signatureYPosition — coordinates for placing the signature on the page.
  • signatureDateFormat — PHP-style date format string for the timestamp shown in the signature.
  • timezone — the timezone used for the signing timestamp.

Embedding PDF Metadata

The API also lets you set PDF metadata fields during signing. This is useful for compliance workflows where documents need proper author, title, and keyword metadata. All metadata fields are optional and include: pdfMetadataAuthor, pdfMetadataTitle, pdfMetadataSubject, pdfMetadataKeywords, pdfMetadataCreator, pdfMetadataProducer, pdfMetadataCreationDate, and pdfMetadataModificationDate.

Verifying Your Certificate First

Before attempting to sign, you can verify that your certificate file and password are correct using the check-certificate-password endpoint. This is especially useful when accepting certificates from end users:

php
<?php
$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: YOUR_ACCESS_KEY',
        'SecretKey: YOUR_SECRET_KEY',
    ],
    CURLOPT_POSTFIELDS => [
        'certificate' => new CURLFile('certificate.pfx', 'application/x-pkcs12'),
        'certificatePassword' => 'your_certificate_password',
    ],
]);

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

$result = json_decode($response, true);

if ($result['success'] ?? false) {
    echo 'Certificate and password are valid.';
} else {
    echo 'Invalid certificate or password.';
}

Streaming the Signed PDF to the Browser

Instead of saving the signed PDF to disk, you can stream it directly to the user's browser. This is useful for download endpoints in web applications:

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('document.pdf', 'application/pdf'),
    ],
]);

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

if (strpos($contentType, 'application/pdf') !== false) {
    header('Content-Type: application/pdf');
    header('Content-Disposition: inline; filename="signed_document.pdf"');
    echo $response;
} else {
    http_response_code(500);
    echo 'Signing failed: ' . $response;
}

Common Mistakes to Avoid

  • Using Bearer token authentication — PDFSignify uses AccessKey and SecretKey headers, not Bearer tokens.
  • Expecting a JSON response — the sign-pdf endpoint returns raw PDF binary data, not JSON.
  • Forgetting the certificate password — the API requires the certificatePassword field even if your certificate has an empty password.
  • Using the wrong Content-Type — when building multipart manually, the boundary in the header must match the boundary in the body exactly.
  • Trying to sign PDFs locally with TCPDF or FPDI — these libraries produce signatures that often fail validation in Adobe Acrobat.

When You Need Certificate Signing

  • Legal contracts that require verifiable proof of signing
  • Enterprise and compliance workflows
  • Government or regulatory documents
  • Any document where tamper detection is required
  • Industries like healthcare, finance, and legal where document integrity matters

Summary

Signing PDFs with a digital certificate in PHP is straightforward when you use the right tool. The PDFSignify API handles the complex cryptographic operations while your PHP code stays clean and focused on business logic. Send the PDF and certificate, get the signed document back — no webhooks, no polling, no complexity.

The best architecture is the simplest one that works. Send the file, get it signed, move on.