Async Conversion
The async conversion endpoint processes your request and immediately acknowledges receipt. Once generation is complete, it sends a callback to the specified webhook, notifying you of the completion.
This method is ideal for handling large-scale document generation, especially when combined with uploading to your S3 storage. The webhook parameter is mandatory in the request body.
Endpoint Details
Method: POST
https://api.pdfbolt.com/v1/async
Success Example
- Request
- Response
- Webhook Request
{
"url": "https://example.com",
"webhook": "https://your-example-webhook.com"
}
{
"requestId": "b32bb5ce-483a-44ab-9239-52c0e3b0ff6c"
}
POST /your-webhook-endpoint HTTP/1.1
Content-Type: application/json
x-pdfbolt-signature: sha256=a1b2c3d4e5f6...
x-pdfbolt-conversion-cost: 1
{
"requestId": "b32bb5ce-483a-44ab-9239-52c0e3b0ff6c",
"status": "SUCCESS",
"errorCode": null,
"errorMessage": null,
"documentUrl": "https://s3.pdfbolt.com/pdfbolt_dd3f57ef-ea17-48a9-930a-8990ee7f52a7_2024-12-30T14-26-23Z.pdf",
"expiresAt": "2024-12-30T21:05:38Z",
"isAsync": true,
"duration": 1403,
"documentSizeMb": 0.04,
"isCustomS3Bucket": false
}
Failure Example
- Request
- Response
- Webhook Request
{
"url": "https://example.com",
"webhook": "https://your-example-webhook.com",
"waitForFunction": "() => document.body.innerText.includes('Ready to Download')"
}
{
"requestId": "7e075770-9c50-4018-a877-fc45c45b7850"
}
POST /your-webhook-endpoint HTTP/1.1
Content-Type: application/json
x-pdfbolt-signature: sha256=f7e8d9c0b1a2...
x-pdfbolt-conversion-cost: 0
{
"requestId": "7e075770-9c50-4018-a877-fc45c45b7850",
"status": "FAILURE",
"errorCode": "CONVERSION_TIMEOUT",
"errorMessage": "Conversion process timed out. Please see https://pdfbolt.com/docs for optimization tips.",
"documentUrl": null,
"expiresAt": null,
"isAsync": true,
"duration": 32150,
"documentSizeMb": null,
"isCustomS3Bucket": false
}
Body Parameters
In this section, you will find only the parameters specific to the /async endpoint. To view the list of common parameters shared across all endpoints, please refer to the Conversion Parameters section.
webhook
Type: string
Required: Yes
Details:
The webhook parameter specifies the URL of your application's endpoint to receive document URLs and additional details. It must be a valid, publicly accessible URL that can process incoming requests from our service.
Usage:
{
"url": "https://example.com",
"webhook": "https://your-app.com/endpoint"
}
customS3PresignedUrl
Type: string
Required: No
Details:
The customS3PresignedUrl allows you to specify a URL for direct upload to your S3 bucket. If not provided, the document is stored in our S3 bucket for 24 hours. See Uploading to Your S3 Bucket for setup details.
Usage:
{
"url": "https://example.com",
"webhook": "https://your-app.com/endpoint",
"customS3PresignedUrl": "https://s3-your-custom-bucket.com?token=abcdef"
}
additionalWebhookHeaders
Type: object
Required: No
Details: This parameter allows you to include up to 10 custom headers in asynchronous webhook requests, providing additional information as needed.
Usage:
{
"url": "https://example.com",
"webhook": "https://your-app.com/endpoint",
"additionalWebhookHeaders": {
"X-Custom-Header-1": "Value1",
"X-Custom-Header-2": "Value2"
}
}
The additionalWebhookHeaders parameter can be used to:
- Add additional context about the request.
- Personalize webhook responses with custom headers.
- Assist in debugging and tracking requests effectively.
retryDelays
Type: Array<number>
Required: No
Details:
The retryDelays parameter defines a custom retry schedule for failed conversions. Each element is the delay in minutes before the next retry attempt, measured from the last failed attempt. The total number of attempts equals the initial attempt plus the number of elements in the retryDelays array.
Validation rules:
- Array of positive integers (minutes), floats are rejected.
- Minimum 1 element, maximum 5 elements (empty array is rejected).
- Values must be in strictly ascending order (e.g.,
[5, 5, 10]is rejected). - All values must be greater than 0.
- Maximum value per element: 1440 (24 hours).
- Webhooks are sent only on final failure or success. Intermediate retry failures do not trigger a webhook callback.
- Only successful conversions consume credits, regardless of how many retries occur.
Usage:
{
"url": "https://example.com",
"webhook": "https://your-app.com/endpoint",
"retryDelays": [1, 5, 15]
}
In the example above, there are 4 total attempts (1 initial + 3 retries). If the first conversion attempt fails:
- Retry 1: 1 minute after the first failure.
- Retry 2: 5 minutes after retry 1 fails.
- Retry 3: 15 minutes after retry 2 fails.
- If all retries fail, the webhook is called with
FAILUREstatus.
Response Parameters
| Parameter | Type | Description | Possible Values | Example Value |
|---|---|---|---|---|
requestId | string (UUID) |
| Any valid UUID | ee57bcb5-b02b-4ae7-af37-6e91549f647d |
Webhook Request Parameters
| Parameter | Type | Description | Possible Values | Example Value |
|---|---|---|---|---|
requestId | string (UUID) |
| Any valid UUID | ee57bcb5-b02b-4ae7-af37-6e91549f647d |
status | string (Enum) |
| SUCCESSFAILURE | SUCCESS |
errorCode | string |
| Refer to errorCode values in the HTTP Status Codes table for all possible values | CONVERSION_TIMEOUT |
errorMessage | string |
| Any string or null | Conversion process timed out. Please see https://pdfbolt.com/docs for optimization tips. |
documentUrl | string (URL) |
| Any valid URL or null | https://s3.pdfbolt.com/pdfbolt_dd3f57ef-ea17-48a9-930a-8990ee7f52a7_2024-12-30T14-26-23Z.pdf |
expiresAt | string (ISO 8601) |
| ISO 8601 datetime string in UTC or null | 2024-12-31T16:45:01Z |
isAsync | boolean |
| true | true |
duration | number |
| Any positive number | 1261 |
documentSizeMb | number |
| Any positive number or null | 0.36 |
isCustomS3Bucket | boolean |
| truefalse | false |
Webhook Signature Verification
Each webhook request includes an x-pdfbolt-signature header, so you can confirm it genuinely came from PDFBolt. The signature is an HMAC-SHA256 hash formatted as:
x-pdfbolt-signature: sha256=<hex-encoded-hmac>
How It Works
- PDFBolt computes
HMAC-SHA256(webhook_signature_key, raw_request_body)using your webhook signature key. - The result is hex-encoded and prefixed with
sha256=. - The signature is sent in the
x-pdfbolt-signatureheader of every webhook request (both success and failure).
Finding Your Webhook Signature Key
You'll find your webhook signature key in the Dashboard – look for the Webhook Signature section on the API Keys page.

Verification Examples
- Always compute the HMAC from the raw request body – before any JSON parsing. If you parse and re-serialize the JSON, key ordering or whitespace may change, producing a different hash.
- Make sure to use a constant-time comparison function to prevent timing attacks.
- Node.js
- Python
- Java
- PHP
- C#
- Go
- Rust
const crypto = require('crypto');
function verifyWebhookSignature(rawBody, signatureHeader, webhookSignatureKey) {
const expected = 'sha256=' + crypto
.createHmac('sha256', webhookSignatureKey)
.update(rawBody, 'utf8')
.digest('hex');
const expectedBuf = Buffer.from(expected);
const receivedBuf = Buffer.from(signatureHeader);
if (expectedBuf.length !== receivedBuf.length) {
return false;
}
return crypto.timingSafeEqual(expectedBuf, receivedBuf);
}
import hmac
import hashlib
def verify_webhook_signature(raw_body: bytes, signature_header: str, webhook_signature_key: str) -> bool:
expected = 'sha256=' + hmac.new(
webhook_signature_key.encode('utf-8'),
raw_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header)
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
public static boolean verifyWebhookSignature(String rawBody, String signatureHeader, String webhookSignatureKey) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(webhookSignatureKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] hash = mac.doFinal(rawBody.getBytes(StandardCharsets.UTF_8));
StringBuilder hex = new StringBuilder();
for (byte b : hash) {
hex.append(String.format("%02x", b));
}
String expected = "sha256=" + hex;
return MessageDigest.isEqual(expected.getBytes(StandardCharsets.UTF_8), signatureHeader.getBytes(StandardCharsets.UTF_8));
}
function verifyWebhookSignature(string $rawBody, string $signatureHeader, string $webhookSignatureKey): bool
{
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $webhookSignatureKey);
return hash_equals($expected, $signatureHeader);
}
using System.Security.Cryptography;
using System.Text;
static bool VerifyWebhookSignature(string rawBody, string signatureHeader, string webhookSignatureKey)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(webhookSignatureKey));
byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(rawBody));
string expected = "sha256=" + Convert.ToHexString(hash).ToLowerInvariant();
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(expected),
Encoding.UTF8.GetBytes(signatureHeader)
);
}
import (
"crypto/hmac"
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
)
func verifyWebhookSignature(rawBody []byte, signatureHeader, webhookSignatureKey string) bool {
mac := hmac.New(sha256.New, []byte(webhookSignatureKey))
mac.Write(rawBody)
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
return subtle.ConstantTimeCompare([]byte(expected), []byte(signatureHeader)) == 1
}
// Cargo.toml dependencies: hmac = "0.12", sha2 = "0.10", hex = "0.4"
use hmac::{Hmac, Mac};
use sha2::Sha256;
type HmacSha256 = Hmac<Sha256>;
fn verify_webhook_signature(raw_body: &[u8], signature_header: &str, webhook_signature_key: &str) -> bool {
let hex_part = match signature_header.strip_prefix("sha256=") {
Some(hex) => hex,
None => return false,
};
let received_bytes = match hex::decode(hex_part) {
Ok(bytes) => bytes,
Err(_) => return false,
};
let mut mac = HmacSha256::new_from_slice(webhook_signature_key.as_bytes())
.expect("HMAC can take key of any size");
mac.update(raw_body);
mac.verify_slice(&received_bytes).is_ok()
}