HubSpot Integration
Generate PDFs from HubSpot records using the DataToPDF API.
Prerequisites
- A DataToPDF account with a Starter plan or higher (API access required)
- An API key generated from Settings > API Key
- A PDF template configured with HubSpot as the data source
- HubSpot connected to your DataToPDF account
Setup
Connect your HubSpot account in DataToPDF:
- Go to Settings > Integrations > HubSpot
- Click Connect HubSpot
- Authorize DataToPDF to access your HubSpot data
API Usage
Endpoint
POST https://datatopdf.app/api/v1/pdf/generate
Request
{
"templateId": "your_template_id",
"recordId": "hubspot_record_id"
}
The recordId should be the HubSpot record ID (numeric ID) for the object type configured in your template (contacts, companies, deals, etc.).
HubSpot Workflows
You can automate PDF generation using HubSpot workflows with either Webhooks or Custom Code Actions.
Option 1: Webhook Action
Use a webhook to call the DataToPDF API directly from a workflow.
Setup
- Go to Automation > Workflows in HubSpot
- Create or edit a workflow (e.g., deal-based workflow)
- Add a Webhook action
- Configure the webhook:
| Setting | Value |
|---|---|
| Method | POST |
| URL | https://datatopdf.app/api/v1/pdf/generate |
| Request headers | X-API-Key: pk_your_api_key_here<br>Content-Type: application/json |
| Request body | See below |
Request Body:
{
"templateId": "clx1234567890",
"recordId": "{{deal.hs_object_id}}"
}
Replace {{deal.hs_object_id}} with the appropriate token for your object type:
- Contacts:
{{contact.hs_object_id}} - Companies:
{{company.hs_object_id}} - Deals:
{{deal.hs_object_id}} - Tickets:
{{ticket.hs_object_id}}
Example: Generate PDF when deal is won
- Create a deal-based workflow
- Set enrollment trigger: Deal stage is Closed Won
- Add Webhook action with the configuration above
- Activate the workflow
Option 2: Custom Code Action
For more control (e.g., saving the PDF to HubSpot Files), use a Custom Code action.
Setup
- Go to Automation > Workflows in HubSpot
- Create or edit a workflow
- Add a Custom code action
- Select Python or Node.js
- Add your API key as a secret:
DATATOPDF_API_KEY
Python Custom Code
import requests
import os
def main(event):
# Get the record ID from the enrolled object
record_id = event.get('object', {}).get('objectId')
template_id = 'clx1234567890' # Your template ID
api_key = os.environ.get('DATATOPDF_API_KEY')
# Generate PDF
response = requests.post(
'https://datatopdf.app/api/v1/pdf/generate',
headers={
'Content-Type': 'application/json',
'X-API-Key': api_key
},
json={
'templateId': template_id,
'recordId': str(record_id)
},
timeout=30
)
if response.ok:
data = response.json()
return {
'outputFields': {
'pdf_generated': True,
'pdf_base64': data['pdf']
}
}
else:
error_data = response.json()
return {
'outputFields': {
'pdf_generated': False,
'error_message': error_data.get('error', 'Unknown error')
}
}
Node.js Custom Code
const axios = require('axios');
exports.main = async (event, callback) => {
const recordId = event.object.objectId;
const templateId = 'clx1234567890'; // Your template ID
const apiKey = process.env.DATATOPDF_API_KEY;
try {
const response = await axios.post(
'https://datatopdf.app/api/v1/pdf/generate',
{
templateId: templateId,
recordId: String(recordId)
},
{
headers: {
'Content-Type': 'application/json',
'X-API-Key': apiKey
},
timeout: 30000
}
);
callback({
outputFields: {
pdf_generated: true,
pdf_base64: response.data.pdf
}
});
} catch (error) {
callback({
outputFields: {
pdf_generated: false,
error_message: error.response?.data?.error || error.message
}
});
}
};
Workflow Use Cases
| Trigger | Action | Description |
|---|---|---|
| Deal stage = Closed Won | Generate invoice PDF | Auto-generate invoice when deal closes |
| Deal stage = Contract Sent | Generate contract PDF | Create contract document for signature |
| Contact created | Generate welcome PDF | Send welcome packet to new contacts |
| Ticket status = Resolved | Generate summary PDF | Create support case summary |
| Form submission | Generate quote PDF | Create quote based on form data |
Combining with Email
After generating a PDF in a workflow, you can:
- Store the base64 output in a custom property
- Use a subsequent webhook to send the PDF via email (using your email service API)
- Use Operations Hub to upload the PDF to HubSpot Files and attach to a note
Examples
cURL
curl -X POST https://datatopdf.app/api/v1/pdf/generate \
-H "Content-Type: application/json" \
-H "X-API-Key: pk_your_api_key_here" \
-d '{
"templateId": "clx1234567890",
"recordId": "12345"
}'
JavaScript (HubSpot Custom Card / Serverless Function)
const axios = require('axios');
exports.main = async (context = {}) => {
const { recordId } = context.parameters;
const templateId = 'clx1234567890';
const apiKey = process.env.DATATOPDF_API_KEY;
try {
const response = await axios.post(
'https://datatopdf.app/api/v1/pdf/generate',
{
templateId,
recordId: recordId.toString()
},
{
headers: {
'Content-Type': 'application/json',
'X-API-Key': apiKey
}
}
);
// Return base64 PDF data
return {
statusCode: 200,
body: {
pdf: response.data.pdf,
contentType: response.data.contentType
}
};
} catch (error) {
return {
statusCode: error.response?.status || 500,
body: {
error: error.response?.data?.error || error.message
}
};
}
};
Python (HubSpot Workflow Custom Code Action)
import requests
import base64
import os
def main(event):
record_id = event.get('inputFields', {}).get('record_id')
template_id = 'clx1234567890'
api_key = os.environ.get('DATATOPDF_API_KEY')
response = requests.post(
'https://datatopdf.app/api/v1/pdf/generate',
headers={
'Content-Type': 'application/json',
'X-API-Key': api_key
},
json={
'templateId': template_id,
'recordId': str(record_id)
}
)
if response.ok:
data = response.json()
return {
'outputFields': {
'pdf_base64': data['pdf'],
'success': True
}
}
else:
error_data = response.json()
return {
'outputFields': {
'error': error_data.get('error', 'Unknown error'),
'success': False
}
}
Node.js (HubSpot Private App)
const hubspot = require('@hubspot/api-client');
const fetch = require('node-fetch');
async function generatePdfForDeal(dealId, templateId) {
const apiKey = process.env.DATATOPDF_API_KEY;
const response = await fetch('https://datatopdf.app/api/v1/pdf/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': apiKey
},
body: JSON.stringify({
templateId: templateId,
recordId: dealId.toString()
})
});
const data = await response.json();
if (!response.ok) {
throw new Error(`${data.code}: ${data.error}`);
}
return Buffer.from(data.pdf, 'base64');
}
// Example: Generate PDF and upload to HubSpot as engagement
async function generateAndAttachPdf(dealId, templateId) {
const hubspotClient = new hubspot.Client({ accessToken: process.env.HUBSPOT_ACCESS_TOKEN });
// Generate PDF
const pdfBuffer = await generatePdfForDeal(dealId, templateId);
// Upload to HubSpot Files API
const uploadResponse = await hubspotClient.files.filesApi.upload({
file: pdfBuffer,
fileName: `Deal_${dealId}_Document.pdf`,
folderId: 'your-folder-id'
});
// Attach to deal as note
await hubspotClient.crm.objects.notes.basicApi.create({
properties: {
hs_note_body: 'PDF document generated',
hs_attachment_ids: uploadResponse.id
},
associations: [{
to: { id: dealId },
types: [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 214 }]
}]
});
return uploadResponse.id;
}
Supported HubSpot Objects
The API supports all standard HubSpot objects that you've configured in your template:
- Contacts
- Companies
- Deals
- Tickets
- Custom Objects
The object type is determined by your template configuration. Make sure the recordId matches the object type configured in your template.
Response Format
Success (200)
{
"pdf": "base64-encoded-pdf-data",
"contentType": "application/pdf"
}
Error Response
{
"error": "Human-readable error message",
"code": "ERROR_CODE"
}
Error Codes
| Code | Description |
|---|---|
UNAUTHORIZED | Missing API key |
INVALID_API_KEY | Invalid or revoked API key |
RATE_LIMIT_EXCEEDED | Too many requests (30/minute limit) |
SUBSCRIPTION_REQUIRED | Active subscription required |
GENERATION_LIMIT_EXCEEDED | Monthly PDF limit reached |
TEMPLATE_NOT_FOUND | Template not found |
CONNECTION_NOT_FOUND | HubSpot connection not configured |
DATA_FETCH_ERROR | Failed to fetch HubSpot data |
GENERATION_FAILED | PDF generation failed |
Rate Limits
- 30 requests per minute per API key
- Monthly generation limits based on your subscription plan
Best Practices
- Store API keys securely - Use environment variables or secrets management
- Handle rate limits - Implement retry logic with exponential backoff
- Validate record IDs - Ensure the record ID exists before making API calls
- Use webhooks for automation - Trigger PDF generation from HubSpot workflow webhooks
- Cache templates - If generating multiple PDFs, minimize template lookups