Skip to main content
Get your Marketing Events sync script up and running quickly with this step-by-step guide.

Prerequisites

Before you begin, ensure you have:

Node.js

Node.js 16+ installed on your system

HubSpot Account

Access to a HubSpot account with Marketing Hub

Private App Access

Permissions to create a HubSpot Private App

Custom Object

A custom deal object or access to the Deals API

Setup Authentication

1

Create a HubSpot Private App

Navigate to SettingsIntegrationsPrivate Apps in your HubSpot account and create a new private app.
You’ll need these scopes:
  • crm.objects.marketing_events.read
  • crm.objects.deals.write
  • crm.objects.deals.read
2

Copy the access token

Once created, copy the access token. You’ll use this to authenticate API requests.
Keep your access token secure and never commit it to version control.
3

Configure the token

You can set the token as an environment variable or directly in the script:
$env:HUBSPOT_PAT = "pat-na1-..."

Configure Pipeline Mapping

The script maps pipeline values from event custom properties to deal stages. Here’s the default mapping from events.js:
events.js
const pipelineMapping = {
  default: "appointmentscheduled",
  732960029: "1067986567",
  733263424: "1068046680",
  733155504: "1067993111",
  732990838: "1067990364",
  733019304: "1068033893",
  733257614: "1068039530",
};
Customize these mappings to match your HubSpot pipeline and deal stage IDs. See Pipeline Mapping for details.

Running the Script

1

Test with mock data

First, test the script logic locally using the provided test file:
node test.js
This runs the sync logic with mock event data and displays the expected output without making real API calls.
🚀 Iniciando test de procesamiento de eventos...
📊 Procesando respuesta de EVENTS_API mock...
📍 Total de eventos en respuesta: 5
📅 Eventos encontrados hoy: 5

--- Evento 1: Test ---
📋 Pipeline encontrado: default
🎯 Deal stage asignado: appointmentscheduled para pipeline: default
✅ Deal simulado creado para "Test"
2

Update API endpoint

Modify the CUSTOM_OBJECT_API constant in events.js to point to your custom object:
events.js
const CUSTOM_OBJECT_API = "https://api.hubapi.com/crm/v3/objects/deals";
// or for custom objects:
// const CUSTOM_OBJECT_API = "https://api.hubapi.com/crm/v3/objects/p12345678_marketing_events";
3

Create a runner script

Create a simple runner to execute the sync:
run.js
const { main } = require('./events.js');

main({}, (result) => {
  console.log('Sync completed:', JSON.stringify(result.outputFields, null, 2));
  
  if (result.outputFields.error) {
    console.error('Error occurred:', result.outputFields.error);
    process.exit(1);
  }
  
  console.log(`
  📊 Summary:
  - Total events: ${result.outputFields.totalEvents}
  - Today's events: ${result.outputFields.todayEvents}
  - Created: ${result.outputFields.createdCount}
  - Skipped: ${result.outputFields.skippedCount}
  - Failed: ${result.outputFields.failedCount}
  `);
});
4

Run the live sync

Execute the sync script to process real Marketing Events:
node run.js
The script will:
  1. Fetch all Marketing Events from your HubSpot account
  2. Filter events created today
  3. Check for existing records
  4. Create new deal records for today’s events
The script runs once when executed. For continuous syncing, schedule it with cron or a task scheduler.

Understanding the Sync Logic

Here’s how the key functions work together:

1. Fetch Events with Pagination

events.js
const getMarketingEvents = async () => {
  const events = [];
  let after;

  do {
    let url = `${EVENTS_API}?limit=100`;
    if (after) url += `&after=${after}`;

    const res = await fetch(url, {
      headers: {
        Authorization: `Bearer ${accessToken}`,
        "Content-Type": "application/json",
      },
    });

    const data = await res.json();
    events.push(...(data.results || []));
    after = data.paging?.next?.after;
  } while (after);

  return events;
};

2. Filter by Today’s Date

events.js
const filterEventsByToday = (events) => {
  const today = new Date();
  today.setHours(0, 0, 0, 0);
  const todayStart = today.getTime();
  const todayEnd = todayStart + 24 * 60 * 60 * 1000 - 1;

  return events.filter((event) => {
    const candidate = findHsCreatedate(event);
    // Convert to milliseconds and check range
    const ms = parseTimestamp(candidate);
    return ms >= todayStart && ms <= todayEnd;
  });
};

3. Check for Duplicates

events.js
// Check by ID
const alreadyExistsById = await eventExists(e.id);
if (alreadyExistsById) {
  console.log(`⏭️  Evento ya existe por ID: ${e.eventName}`);
  continue;
}

// Check by name
const nameExists = await recordExistsByName(e.eventName);
if (nameExists) {
  console.log(`⏭️  Evento ya existe por nombre: ${e.eventName}`);
  continue;
}

4. Map Pipeline and Stage

events.js
const pipelineValue = getPipelineFromCustomProperties(eventData.customProperties);
const dealStage = getDealStageByPipeline(pipelineValue);

5. Create the Deal

events.js
const body = {
  properties: {
    dealname: eventData.eventName,
    fecha_hora_de_inicio: eventData.startDateTime,
    fecha_hora_fin: eventData.endDateTime,
    organizador_evento: mapOrganizerToValidOption(eventData.eventOrganizer),
    pipeline: pipelineValue,
    dealstage: dealStage,
    // ... more properties
  },
};

const res = await fetch(CUSTOM_OBJECT_API, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${accessToken}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(body),
});

Troubleshooting

Your access token is invalid or expired. Verify:
  • The token is correctly set in the environment variable or script
  • The private app hasn’t been deleted or regenerated
  • You’re using the correct token format (pat-na1-...)
If todayEvents: 0 is returned:
  • Verify that events were actually created today in HubSpot
  • Check the hs_createdate field format in your events
  • Review the date filtering logic and timezone settings
If you see VALIDATION_ERROR when creating deals:
  • A record with the same name already exists (the script checks for this)
  • Required fields are missing from the custom object
  • Property values don’t match allowed options (e.g., organizer values)
If you’re syncing large volumes:
  • Add delays between API calls
  • Reduce the batch size
  • Consider upgrading your HubSpot API limits

Next Steps

Configure Mappings

Customize pipeline and organizer mappings for your setup

Understand Filtering

Learn how date filtering handles different timestamp formats

View All Functions

Explore the complete function reference

Troubleshooting Guide

Resolve common issues and errors

Build docs developers (and LLMs) love