PHP · Invoices

Generate an invoice PDF in PHP with one API request

Call RenderLark from PHP, receive PDF bytes directly, validate the response, and save an invoice safely before sending it to a customer.

By the RenderLark team · 6 min read · Updated

Choose a public test invoice, not a customer record

If your invoicing application already produces an HTML page, you can keep that template and request a PDF from your backend. RenderLark's synchronous endpoint waits for the render and returns the PDF bytes in the same HTTP response. Your PHP application remains responsible for naming, storing, and delivering the file.

Start with our fictional invoice sample. Do not make confidential invoices publicly accessible just to render them. A URL renderer does not inherit your browser's login cookies. For sensitive documents, generate the HTML in your authorized backend and submit the HTML field instead; HTML rendering requires a paid plan. Remote images and styles must still be publicly reachable.

Send JSON and validate the binary response

Create an API key in your dashboard and store it as RENDERLARK_API_KEY in your server environment. The key belongs on your server, never in a public web page or a committed PHP file. This example needs PHP 8 with cURL and a writable private storage directory outside the public web root.

POST https://renderlark.com/v1/generate accepts html (a public URL or HTML source) and apiKey. A successful request returns HTTP 200 with application/pdf. An error returns JSON, so saving every response with a .pdf extension would create corrupt attachments.

PHP

<?php
declare(strict_types=1);
$apiKey = getenv('RENDERLARK_API_KEY');
if (!$apiKey) throw new RuntimeException('Missing API key');

$data = json_encode([
    'html' => 'https://renderlark.com/samples/invoice.html',
    'apiKey' => $apiKey,
], JSON_THROW_ON_ERROR);

// Persist one key per invoice revision if you add retries.
$idempotencyKey = 'invoice-demo-001-revision-1';
$ch = curl_init('https://renderlark.com/v1/generate');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $data,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT => 90,
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'Idempotency-Key: ' . $idempotencyKey,
    ],
]);
$pdf = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$type = (string) curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
$networkError = curl_errno($ch);
curl_close($ch);

if ($networkError || $status !== 200 ||
    stripos($type, 'application/pdf') !== 0 ||
    !is_string($pdf) || !str_starts_with($pdf, '%PDF-')) {
    throw new RuntimeException('PDF generation failed; HTTP ' . $status);
}

// Configure this directory; do not take it from a query parameter.
$directory = '/var/lib/my-app/invoices';
$target = $directory . '/invoice-demo-001.pdf';
$temporary = tempnam($directory, 'pdf-');
if ($temporary === false) throw new RuntimeException('Cannot create file');
try {
    if (file_put_contents($temporary, $pdf, LOCK_EX) !== strlen($pdf)) {
        throw new RuntimeException('Incomplete PDF write');
    }
    if (!rename($temporary, $target)) throw new RuntimeException('Cannot save PDF');
} finally {
    if (is_file($temporary)) unlink($temporary);
}

// Mark the invoice generated only after successful storage.
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="invoice-demo-001.pdf"');
header('Cache-Control: private, no-store');
echo $pdf;

Fit the request into your invoice workflow

Run invoice generation in a background task when possible. After the PDF has been validated and saved, update your invoice record and enqueue its email. If rendering or storage fails, leave the record eligible for a retry. Use a separate sent flag or message identifier so an email retry does not send the same invoice twice.

The compatibility endpoint follows the html/apiKey request shape and binary PDF response of common HTML-to-PDF integrations. It is not a promise that every option from another provider is supported. For paper size, print media, and other render options, use the asynchronous API described in the Node.js tutorial.

A temporary timeout does not necessarily mean the underlying render failed. Add bounded retries using the same request body, API key, and Idempotency-Key; see the retry tutorial before using this example for batch processing. Downloaded PDFs are yours to store, but files on RenderLark are temporary and expire according to your plan.

  • Use prepared database queries and authorize access to every invoice.
  • Keep API keys and generated invoices out of publicly served directories.
  • Never disable TLS certificate verification to work around a connection error.
  • Validate HTTP status, content type, and PDF signature before saving or emailing.

Further reading

Keep building