Browse documentation
Integrations
Send personalized video emails from HubSpot.
Welcome every new lead with a video made just for them. Follow these steps to add an animated preview and a personal video link to your HubSpot emails, without Zapier.
1. Before you start
You'll need a BHuman video template, your BHuman API credentials and enough credits to create a test video. Use an email address you control for your first test.
In HubSpot, you'll need Data Hub Professional or Enterprise for the Custom code action and Marketing Hub Professional or Enterprise for automated marketing emails. Check that both features are available in your account before you begin. HubSpot workflow requirements, automated email requirements.
You'll copy two short code examples below. They let HubSpot ask BHuman to create a video and check when it's ready.
2. Choose your video and add contact fields
Start with the video template you'd like your leads to receive. In BHuman, find its campaign ID and the names of the fields you want to personalize.
The code below uses name and conference as example fields. It takes the name from HubSpot's First Name property. Replace YOUR_CAMPAIGN_ID, the field names and their values to match your BHuman template. Keep the fields in the same order in the variables and names lists.
Next, create these four contact properties in HubSpot. Choose Single-line text for each, in the Contact information group. Use the internal names exactly as shown so the email and workflow can find them.
| Property label | Internal name | What it stores |
|---|---|---|
| BHuman Video ID | bhuman_video_id | The video created for this contact |
| BHuman Video Status | bhuman_video_status | Whether the video is ready |
| BHuman Video URL | bhuman_video_url | The link to watch the video |
| BHuman GIF URL | bhuman_gif_url | The moving preview shown in the email |
3. Start with your own email address
In HubSpot, open Workflows and create a contact-based workflow. For now, set the enrollment rule to Email is equal to your own test address. Leave re-enrollment off so the same contact doesn't receive the welcome email again.

Once your test looks right, you can change the rule to your new-lead form or another lead source.
4. Connect HubSpot to BHuman
Add Data ops > Custom code, select Node.js 24.x, and name the action Generate BHuman video once.
Add two secrets named BHUMAN_CLIENT_ID and BHUMAN_CLIENT_SECRET, using the values from your BHuman API settings. Select both secrets for this action. HubSpot keeps these values private, so you don't need to put them in the code.
Under Property to include in code, add these inputs:
| Input name | HubSpot contact property |
|---|---|
first_name | First Name |
existing_video_id | BHuman Video ID |

Paste the code below, then replace YOUR_CAMPAIGN_ID and the example fields with your own template details.
Create your video
// HubSpot custom code action. Runtime: Node.js 24.x.
// Secrets: BHUMAN_CLIENT_ID, BHUMAN_CLIENT_SECRET.
// Inputs: first_name, existing_video_id (optional).
// String outputs: video_id, video_status, message.
exports.main = async (event) => {
const result = (video_id, video_status, message) => ({
outputFields: { video_id, video_status, message },
});
const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const existing = String(event.inputFields.existing_video_id || '').trim();
if (existing) {
return result(existing, uuid.test(existing) ? 'processing' : 'needs_review',
'Existing generation retained. No new video requested.');
}
const name = String(event.inputFields.first_name || '').trim();
if (!name) return result('', 'failed', 'First name is required.');
const clientId = process.env.BHUMAN_CLIENT_ID;
const clientSecret = process.env.BHUMAN_CLIENT_SECRET;
if (!clientId || !clientSecret) {
return result('', 'failed', 'Attach both BHuman secrets to this action.');
}
try {
// Submit once. A timeout can follow an accepted, billable request.
const response = await fetch(
'https://studio.bhuman.ai/api/ai_studio/pipeline/campaign', {
method: 'POST',
headers: {
Authorization: 'Basic ' + Buffer.from(clientId + ':' + clientSecret).toString('base64'),
'Content-Type': 'application/json',
},
signal: AbortSignal.timeout(10000),
body: JSON.stringify({
campaign_id: 'YOUR_CAMPAIGN_ID',
generation_mode: 'keep_original',
variables: ['name', 'conference'],
names: [[name, 'HubSpot demo']],
}),
});
const body = await response.json();
const ids = body.result;
if (response.ok && body.code === 200 && Array.isArray(ids) &&
ids.length === 1 && typeof ids[0] === 'string' && uuid.test(ids[0])) {
return result(ids[0], 'processing', 'Video accepted. Save its ID before checking completion.');
}
return result('', 'needs_review',
'Generation did not return one valid ID. Check BHuman before retrying.');
} catch {
// Returning a review state prevents automatic retry of a paid POST.
return result('', 'needs_review',
'Request outcome uncertain. Check BHuman and retain any accepted ID.');
}
};Under Data outputs, add video_id, video_status and message. Set each to String, then save the action.
Choose Test action and select your test contact. This creates a real video and uses your normal BHuman credits, so run it once and keep the video ID it returns. A processing status means BHuman has started making the video.

5. Save the video ID and add a short wait
Add CRM > Edit record. Set BHuman Video ID to the video_id output from Generate BHuman video once. Choose the output from HubSpot's picker rather than typing its name.
Then add Delay > Set amount of time > 5 minutes. This gives BHuman time to prepare the video and its animated preview.
If you already ran the code test, save its returned ID in your test contact's BHuman Video ID field before enrolling that contact. The workflow will reuse the saved video instead of asking for another one.
If a request times out, check your BHuman campaign before trying again. The video may already be there.
6. Get the video link and animated preview
Add another Custom code action named Check saved BHuman video, using Node.js 24.x. Add an input named video_id and map it to the contact's BHuman Video ID property.
Paste this code. It checks the video you already created and returns its viewing link and GIF preview.
Get the video and GIF links
// HubSpot custom code action. Runtime: Node.js 24.x. No secrets required.
// Input: video_id. String outputs: video_id, video_status, video_url, gif_url, message.
exports.main = async (event) => {
const id = String(event.inputFields.video_id || '').trim();
const result = (video_status, video_url = '', gif_url = '', message = '') => ({
outputFields: { video_id: id, video_status, video_url, gif_url, message },
});
const isHttps = (value) => {
try { return typeof value === 'string' && new URL(value).protocol === 'https:'; }
catch { return false; }
};
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)) {
return result('needs_review', '', '', 'A valid saved generation ID is required.');
}
try {
const response = await fetch(
'https://studio.bhuman.ai/api/ai_studio/generated_video_by_id?id=' + encodeURIComponent(id),
{ signal: AbortSignal.timeout(10000) });
const body = await response.json();
if (!response.ok || body.code !== 200 || !body.result || body.result.id !== id) {
return result('needs_review', '', '', 'Status lookup failed. Keep this ID and retry the lookup.');
}
const video = body.result;
if (video.status === 'failed') return result('failed', '', '', 'BHuman reported a failed render.');
if (video.status === 'succeeded' && isHttps(video.share_url) && isHttps(video.url) && isHttps(video.gif)) {
return result('ready', video.share_url, isHttps(video.gif) ? video.gif : '', 'Video ready.');
}
return result('processing', '', '', 'Not ready. Delay and check this same ID again.');
} catch {
return result('needs_review', '', '', 'Status lookup interrupted. Keep this ID and retry the lookup.');
}
};Add these String outputs: video_id, video_status, video_url, gif_url and message. Save the action.
Add an Edit record action after it. Set BHuman Video Status to the video_status output from Check saved BHuman video.
7. Send only when the video is ready
Add Branch > One property or action output. Choose video_status from Check saved BHuman video and create a branch where it is equal to ready.
On that branch, add an Edit record action to save video_url into BHuman Video URL. Add another to save gif_url into BHuman GIF URL. Your email will use these two fields to show the right preview and open the right video for each person.

Keep other results on a separate Hold: render not ready path. This setup checks once after five minutes. For videos that take longer, add a few more waits and checks on that path, then leave anything still unfinished for review. Always check the same saved video ID.
8. Create your video email
Use HubSpot's full Marketing > Email editor to create an automated email. Choose a simple template and add an HTML module for your video message.
Paste the example below. The greeting uses the contact's first name. The image uses their BHuman GIF URL, and clicking it opens their BHuman Video URL. Change the words and signature to suit your business.
Animated video email
<p>Hi {{ contact.firstname }},</p>
<p>I made a short video to welcome you. Tap the preview to watch!</p>
<p>
<a href="{{ contact.bhuman_video_url }}">
<img src="{{ contact.bhuman_gif_url }}"
alt="Watch your personalized video"
width="280"
style="display:block;width:280px;max-width:100%;height:auto;border:0;">
</a>
</p>
<p><a href="{{ contact.bhuman_video_url }}">Watch your video</a></p>
<p>Don<br>BHuman</p>Choose a subject such as A quick video to welcome you, select your sender details and include your usual business footer and unsubscribe link. Keep the text link below the preview so people can still watch if their email app doesn't display images.

Save and publish your automated email, then select it in a Send email action after the two video properties are saved.
9. Send yourself a test
Keep your enrollment rule limited to your own address, turn on the workflow and enroll your test contact. After the wait, check that BHuman Video Status says ready and both the video link and GIF link are filled in.

Open the workflow's action logs to confirm the email was sent.

Open the received email and click the moving preview. It should take you to the video made for your test contact. If the email isn't in your inbox, check Spam too. Before sending to leads, connect your sending domain in HubSpot and test delivery from that address.
10. Welcome your new leads
Happy with your test? Change the enrollment rule to your lead source, such as a form submission. Require a first name and make sure contacts are eligible to receive your marketing email.
When you turn it on, choose only contacts who meet the criteria after the workflow is turned on if you'd like to start with new leads. Keep re-enrollment off to avoid sending the welcome email twice.
Need a hand?
| What you're seeing | Try this |
|---|---|
| You can't find Custom code or automated emails | Check that your HubSpot plan includes both features. |
| The video is still processing | Wait a little longer and check the same video ID again. |
| The preview or link is missing | Check that both contact properties are saved before Send email. |
| The video request needs review | Check your BHuman campaign before sending another request. |
| The email hasn't arrived | Check HubSpot's action logs, the contact's email eligibility and Spam. |
Download the guide and code examples, or visit the BHuman API reference.
Here's what your lead receives
A personal greeting, a moving preview and one click to watch their video. Here's the email from the walkthrough, received in Gmail.
