Custom Integrations
Comprehensive guide to integrate Pulsora with any platform, CRM, or custom system. Covers Shopify, WordPress, Salesforce, HubSpot, manual invoicing, headless CMS, mobile apps, and custom payment processors.
1. Initialize Pulsora in the Frontend
Every integration starts by installing @pulsora/core (or the CDN snippet) and capturing the two attribution values you need later:
import { Pulsora } from '@pulsora/core';
const pulsora = new Pulsora();
pulsora.init({ apiToken: 'pub_123' });
async function getAttribution() {
return {
visitor_fingerprint: await pulsora.getVisitorFingerprint(),
session_id: pulsora.getSessionId(),
};
}
2. Forward Attribution to Your Backend
Whenever a conversion flow starts, send the fingerprint + session to your server. Examples:
- Form submission API payload
- Hidden fields inside a classic HTML form
- Custom SDK metadata (Stripe, Paddle, Chargebee, LemonSqueezy, Shopify, etc.)
- Native mobile bridge that posts to your backend
const attribution = await getAttribution();
await fetch('/api/lead', {
method: 'POST',
body: JSON.stringify({
...leadData,
...attribution,
}),
});
3. Emit Revenue or Lifecycle Events
Use @pulsora/revenue anywhere you finalize payments, invoices, or offline deals:
import { Revenue } from '@pulsora/revenue';
const revenue = new Revenue({ apiToken: process.env.PULSORA_SECRET! });
async function recordDeal(payload: DealPayload) {
await revenue.track({
visitor_fingerprint: payload.visitor_fingerprint ?? 'manual',
session_id: payload.session_id ?? 'manual',
customer_id: payload.customer_id,
amount: payload.amount,
currency: payload.currency ?? 'USD',
transaction_id: payload.reference,
payment_processor: payload.source ?? 'internal',
event_type: payload.type ?? 'purchase',
metadata: payload.metadata,
});
}
If you ingest revenue from third-party CRMs (Salesforce, HubSpot), map their lifecycle changes to Pulsora event types.
4. Handle Failures & Retries
- Wrap
revenue.track()intry/catchand log to your monitoring system. - Add idempotency by reusing
transaction_id—Pulsora deduplicates identical IDs. - Queue retries when your downstream systems are unavailable (BullMQ, SQS, etc.).
5. Server-Side Event Tracking
If you need to record additional server-side analytics (e.g. API usage), send raw requests to POST /api/ingest:
await fetch('https://pulsora.co/api/ingest', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Token': process.env.PULSORA_SECRET!,
},
body: JSON.stringify({
type: 'event',
data: {
visitor_fingerprint: fingerprint,
session_id: sessionId,
event_name: 'api_call',
event_data: { endpoint: '/v1/exports' },
timestamp: Date.now(),
},
}),
});
This mirrors what the core SDK sends from the browser.
6. Privacy & Compliance
Custom integrations must respect Pulsora’s privacy guarantees:
- Do not send PII in event names or metadata (use IDs instead of emails).
- Hash customer IDs on your backend if required by policy.
Platform-Specific Examples
Shopify Integration
Track Shopify orders with full visitor attribution:
<!-- theme.liquid - Add Pulsora to all pages -->
<script src="https://cdn.pulsora.co/v1/pulsora.min.js" data-token="pub_xxx"></script>
<!-- checkout.liquid or additional scripts -->
<script>
// Capture attribution and store in session
async function captureAttribution() {
const fingerprint = await window.pulsora.getVisitorFingerprint();
const sessionId = window.pulsora.getSessionId();
// Store in sessionStorage for checkout
sessionStorage.setItem('pulsora_fingerprint', fingerprint);
sessionStorage.setItem('pulsora_session', sessionId);
}
// Run on page load
window.addEventListener('load', captureAttribution);
</script>
Backend webhook handler (Node.js):
// webhooks/shopify-orders.js
import { Revenue } from '@pulsora/revenue';
import crypto from 'crypto';
const revenue = new Revenue({ apiToken: process.env.PULSORA_SECRET! });
// Verify Shopify HMAC
function verifyShopifyWebhook(data, hmacHeader) {
const hash = crypto
.createHmac('sha256', process.env.SHOPIFY_WEBHOOK_SECRET!)
.update(data, 'utf8')
.digest('base64');
return crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(hmacHeader));
}
export async function POST(req: Request) {
const body = await req.text();
const hmac = req.headers.get('X-Shopify-Hmac-SHA256');
if (!verifyShopifyWebhook(body, hmac)) {
return Response.json({ error: 'Invalid signature' }, { status: 401 });
}
const order = JSON.parse(body);
// Get attribution from order notes or metafields
const fingerprint = order.note_attributes?.find(
attr => attr.name === 'pulsora_fingerprint'
)?.value;
const sessionId = order.note_attributes?.find(
attr => attr.name === 'pulsora_session'
)?.value;
await revenue.track({
visitor_fingerprint: fingerprint,
session_id: sessionId,
customer_id: order.customer.id.toString(),
amount: parseFloat(order.total_price),
currency: order.currency,
transaction_id: order.id.toString(),
payment_processor: 'shopify',
event_type: 'purchase',
metadata: {
order_number: order.order_number,
items_count: order.line_items.length,
discount_codes: order.discount_codes.map(d => d.code).join(',')
}
});
return Response.json({ success: true });
}
WordPress / WooCommerce
Add Pulsora to WordPress theme:
<!-- functions.php -->
<?php
function add_pulsora_analytics() {
?>
<script src="https://cdn.pulsora.co/v1/pulsora.min.js" data-token="pub_xxx"></script>
<script>
// Store attribution in WordPress session
window.addEventListener('load', async function() {
const fingerprint = await window.pulsora.getVisitorFingerprint();
const sessionId = window.pulsora.getSessionId();
// Send to WordPress backend via AJAX
jQuery.post(ajaxurl, {
action: 'store_pulsora_attribution',
fingerprint: fingerprint,
session_id: sessionId,
nonce: '<?php echo wp_create_nonce("pulsora_nonce"); ?>'
});
});
</script>
<?php
}
add_action('wp_footer', 'add_pulsora_analytics');
// Store in WooCommerce order
add_action('woocommerce_checkout_create_order', function($order, $data) {
if (isset($_SESSION['pulsora_fingerprint'])) {
$order->update_meta_data('_pulsora_fingerprint', $_SESSION['pulsora_fingerprint']);
$order->update_meta_data('_pulsora_session', $_SESSION['pulsora_session']);
}
}, 10, 2);
// Track completed orders
add_action('woocommerce_payment_complete', function($order_id) {
$order = wc_get_order($order_id);
$data = [
'visitor_fingerprint' => $order->get_meta('_pulsora_fingerprint'),
'session_id' => $order->get_meta('_pulsora_session'),
'customer_id' => $order->get_customer_id(),
'amount' => $order->get_total(),
'currency' => $order->get_currency(),
'transaction_id' => $order_id,
'payment_processor' => 'woocommerce'
];
// Send to your backend that uses @pulsora/revenue
wp_remote_post('https://yourbackend.com/api/track-revenue', [
'body' => json_encode($data),
'headers' => ['Content-Type' => 'application/json']
]);
});
Salesforce Integration
Sync Salesforce opportunities with Pulsora:
// services/salesforce-sync.ts
import { Revenue } from '@pulsora/revenue';
import jsforce from 'jsforce';
const revenue = new Revenue({ apiToken: process.env.PULSORA_SECRET! });
const sf = new jsforce.Connection({
loginUrl: 'https://login.salesforce.com',
});
await sf.login(process.env.SF_USERNAME!, process.env.SF_PASSWORD!);
// Track opportunity closed-won
async function trackClosedOpportunity(opportunityId: string) {
const opp = await sf.sobject('Opportunity').retrieve(opportunityId);
// Get attribution from custom fields
const fingerprint = opp.Pulsora_Fingerprint__c;
const sessionId = opp.Pulsora_Session__c;
await revenue.track({
visitor_fingerprint: fingerprint,
session_id: sessionId,
customer_id: opp.AccountId,
amount: opp.Amount,
currency: opp.CurrencyIsoCode || 'USD',
transaction_id: opp.Id,
payment_processor: 'salesforce',
event_type: 'purchase',
customer_subscription_id:
opp.Type === 'Renewal' ? opp.AccountId : undefined,
is_recurring: opp.Type === 'New Business' || opp.Type === 'Renewal',
mrr_impact: opp.Type === 'New Business' ? opp.Amount / 12 : 0,
metadata: {
opportunity_name: opp.Name,
stage: opp.StageName,
lead_source: opp.LeadSource,
close_date: opp.CloseDate,
},
});
}
// Listen for Opportunity updates via Salesforce Platform Events
sf.streaming.topic('OpportunityUpdates').subscribe(async (message) => {
if (message.sobject.StageName === 'Closed Won') {
await trackClosedOpportunity(message.sobject.Id);
}
});
HubSpot Integration
// services/hubspot-sync.ts
import { Revenue } from '@pulsora/revenue';
import { Client } from '@hubspot/api-client';
const revenue = new Revenue({ apiToken: process.env.PULSORA_SECRET! });
const hubspot = new Client({ accessToken: process.env.HUBSPOT_ACCESS_TOKEN! });
// Webhook handler for deal updates
export async function handleHubSpotWebhook(req: Request) {
const payload = await req.json();
for (const event of payload) {
if (
event.subscriptionType === 'deal.propertyChange' &&
event.propertyName === 'dealstage' &&
event.propertyValue === 'closedwon'
) {
const dealId = event.objectId;
// Get deal details
const deal = await hubspot.crm.deals.basicApi.getById(dealId, [
'amount',
'dealname',
'pulsora_fingerprint', // Custom property
'pulsora_session', // Custom property
'closedate',
]);
await revenue.track({
visitor_fingerprint: deal.properties.pulsora_fingerprint,
session_id: deal.properties.pulsora_session,
customer_id: deal.associations?.companies?.[0]?.id.toString(),
amount: parseFloat(deal.properties.amount),
currency: 'USD',
transaction_id: dealId,
payment_processor: 'hubspot',
event_type: 'purchase',
metadata: {
deal_name: deal.properties.dealname,
close_date: deal.properties.closedate,
},
});
}
}
return Response.json({ success: true });
}
Manual Invoice Tracking
For businesses with manual invoicing:
// api/invoices/route.ts
import { Revenue } from '@pulsora/revenue';
const revenue = new Revenue({ apiToken: process.env.PULSORA_SECRET! });
export async function POST(req: Request) {
const invoice = await req.json();
// Track manual invoice
await revenue.track({
// Attribution may be null for manual deals
visitor_fingerprint: invoice.attribution?.fingerprint || 'manual',
session_id: invoice.attribution?.session || 'manual',
customer_id: invoice.customer_id,
amount: invoice.amount,
currency: invoice.currency || 'USD',
transaction_id: invoice.invoice_number,
payment_processor: 'manual',
event_type: 'purchase',
metadata: {
invoice_number: invoice.invoice_number,
payment_method: invoice.payment_method,
terms: invoice.payment_terms,
issued_date: invoice.issued_date,
due_date: invoice.due_date,
notes: invoice.notes,
},
});
return Response.json({ success: true });
}
// Usage from admin panel
const response = await fetch('/api/invoices', {
method: 'POST',
body: JSON.stringify({
customer_id: 'cust_123',
amount: 5000,
currency: 'USD',
invoice_number: 'INV-2024-001',
payment_method: 'Bank Transfer',
payment_terms: 'Net 30',
issued_date: '2024-01-15',
due_date: '2024-02-14',
notes: 'Annual enterprise contract',
}),
});
Headless CMS (Contentful, Sanity)
// Track content conversions (e.g., gated content downloads)
import { Pulsora } from '@pulsora/core';
const pulsora = new Pulsora();
pulsora.init({ apiToken: 'pub_xxx' });
async function trackContentDownload(contentId: string, contentTitle: string) {
// Get attribution
const fingerprint = await pulsora.getVisitorFingerprint();
const sessionId = pulsora.getSessionId();
// Track client-side event
await pulsora.event('content_download', {
content_id: contentId,
content_title: contentTitle,
});
// Send to backend for lead scoring
await fetch('/api/leads/content-download', {
method: 'POST',
body: JSON.stringify({
visitor_fingerprint: fingerprint,
session_id: sessionId,
content_id: contentId,
content_title: contentTitle,
email: userEmail, // If collected
}),
});
}
// Track form submissions
document.querySelector('form').addEventListener('submit', async (e) => {
e.preventDefault();
const fingerprint = await pulsora.getVisitorFingerprint();
const sessionId = pulsora.getSessionId();
const formData = new FormData(e.target);
await fetch('/api/leads', {
method: 'POST',
body: JSON.stringify({
...Object.fromEntries(formData),
visitor_fingerprint: fingerprint,
session_id: sessionId,
}),
});
});
Mobile App (React Native)
// services/analytics.ts
import { Pulsora } from '@pulsora/core';
import AsyncStorage from '@react-native-async-storage/async-storage';
class MobileAnalytics {
private pulsora: Pulsora;
constructor() {
this.pulsora = new Pulsora();
this.init();
}
async init() {
await this.pulsora.init({
apiToken: 'pub_xxx',
});
}
async trackPurchase(purchase: {
productId: string;
amount: number;
currency: string;
}) {
const fingerprint = await this.pulsora.getVisitorFingerprint();
const sessionId = this.pulsora.getSessionId();
// Send to backend for revenue tracking
await fetch('https://api.yourapp.com/revenue', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
visitor_fingerprint: fingerprint,
session_id: sessionId,
...purchase,
}),
});
}
}
export const analytics = new MobileAnalytics();
// Usage in purchase flow
import { analytics } from './services/analytics';
async function handlePurchase(productId: string) {
// Process payment with your payment provider
const payment = await processPayment(productId);
// Track with Pulsora
await analytics.trackPurchase({
productId,
amount: payment.amount,
currency: payment.currency,
});
}
Troubleshooting
Attribution Data Not Captured
Problem: Fingerprint/session not available during checkout
Solutions:
- Store in sessionStorage:
// On page load
const fingerprint = await pulsora.getVisitorFingerprint();
sessionStorage.setItem('pulsora_fp', fingerprint);
sessionStorage.setItem('pulsora_sid', pulsora.getSessionId());
// During checkout
const fp = sessionStorage.getItem('pulsora_fp');
const sid = sessionStorage.getItem('pulsora_sid');
- Use cookies (GDPR-compliant):
// Store attribution in first-party cookie
document.cookie = `pulsora_fp=${fingerprint}; path=/; max-age=1800; secure; samesite=strict`;
- Hidden form fields:
<form action="/checkout" method="POST">
<input type="hidden" name="visitor_fingerprint" id="fp" />
<input type="hidden" name="session_id" id="sid" />
<!-- Other fields -->
</form>
<script>
window.addEventListener('load', async () => {
const fp = await window.pulsora.getVisitorFingerprint();
const sid = window.pulsora.getSessionId();
document.getElementById('fp').value = fp;
document.getElementById('sid').value = sid;
});
</script>
CRM Sync Issues
Problem: Opportunities/deals not syncing to Pulsora
Debugging:
// Add detailed logging
async function syncDeal(dealId: string) {
console.log('[CRM Sync] Starting:', dealId);
try {
const deal = await fetchDealFromCRM(dealId);
console.log('[CRM Sync] Deal data:', deal);
const result = await revenue.track({
visitor_fingerprint: deal.fingerprint || 'crm-manual',
session_id: deal.session || 'crm-manual',
customer_id: deal.customer_id,
amount: deal.amount,
currency: deal.currency,
transaction_id: dealId,
payment_processor: 'crm',
event_type: 'purchase',
});
console.log('[CRM Sync] Success:', result);
} catch (error) {
console.error('[CRM Sync] Error:', error);
// Retry with exponential backoff
await retryWithBackoff(() => syncDeal(dealId));
}
}
Manual Revenue Not Attributed
Issue: Manual deals show "Unknown source"
Solution:
For offline/manual deals without attribution, track as manual:
await revenue.track({
visitor_fingerprint: 'manual-entry',
session_id: 'manual-entry',
customer_id: customerId,
amount: amount,
currency: 'USD',
transaction_id: invoiceNumber,
payment_processor: 'manual',
event_type: 'purchase',
metadata: {
source: 'manual_invoice',
sales_rep: salesRepId,
notes: 'Enterprise deal closed offline',
},
});
Then segment manual vs attributed revenue in your dashboard.
Architecture Checklist
- ✅ Capture fingerprint + session in every conversion flow
- ✅ Store attribution alongside the transaction in your database
- ✅ Send revenue events with the same transaction ID for deduplication
- ✅ Retry failures with exponential backoff
- ✅ Monitor webhook endpoints (5xx responses, timeouts)
- ✅ Handle null/missing attribution gracefully
- ✅ Log all CRM/platform sync operations
- ✅ Test with real transactions in sandbox mode
- ✅ Document custom fields in CRM for attribution data
- ✅ Set up alerts for sync failures
Next Steps
- Stripe Integration - Pre-built Stripe integration
- Paddle Integration - Pre-built Paddle integration
- Revenue API Reference - Complete API documentation
- Revenue Attribution - Configure revenue tracking and attribution