Events API Guide
Events API Guide
The Events API allows you to track all interactions and activities with your contacts. Events are the foundation of engagement tracking in Sure Send CRM, capturing everything from page views to form submissions to property searches.
Overview
Events represent actions or interactions that occur with your contacts. They help you:
- Build engagement timelines
- Track website activity
- Monitor property interest
- Measure campaign effectiveness
- Trigger automations based on behavior
Common Event Types:
Send as type | Read back as canonicalEventType | Meaning |
|---|---|---|
page_view | Viewed Page | Website page viewed |
property_view | Viewed Property | Property listing viewed |
property_save | Saved Property | Property saved to favorites |
property_search | Property Search | Property search performed |
saved_property_search | Saved Property Search | Search saved for alerts |
form_submission | Form Submission | Contact form submitted |
property_inquiry | Property Inquiry | Inquiry about a specific property |
seller_inquiry | Seller Inquiry | Seller or home valuation inquiry |
general_inquiry | General Inquiry | Direct inquiry submitted |
registration | Registration | Account or portal registration |
incoming_call | Incoming Call | Inbound phone call |
visited_open_house | Visited Open House | Open house attendance |
Custom types are fully supported. Send anything you need — email_opened, email_clicked,
appointment_scheduled, document_downloaded — and it is stored and returned exactly as you
wrote it.
Sending an event type vs. matching on oneThe
typeyou send is stored verbatim and returned aseventType. Every event also carries
canonicalEventType, a normalized value that folds all spellings of the same concept together.
When you read events back, match oncanonicalEventType— see
Matching Event Types.
Creating Events
Basic Event
Endpoint: POST /api/partner/events
curl -X POST https://api.suresend.ai/api/partner/events \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "page_view",
"personId": "person-uuid",
"pageUrl": "https://example.com/about",
"pageTitle": "About Us"
}'Response:
{
"person": { "id": "person-uuid", "firstName": "John", "lastName": "Doe" },
"event": {
"id": "event-uuid",
"type": "page_view",
"canonicalEventType": "Viewed Page",
"personId": "person-uuid",
"occurredAt": "2025-10-16T12:00:00Z",
"created": "2025-10-16T12:00:00Z"
},
"wasPersonCreated": false,
"wasCompanyCreated": false
}
The create response nests the event undereventand names the raw typetype(the list anddetail endpoints name the same value
eventType).canonicalEventTypeis spelled the same way
everywhere.
Page View Event
Track when contacts visit pages on your website:
curl -X POST https://api.suresend.ai/api/partner/events \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "page_view",
"personId": "person-uuid",
"pageUrl": "https://example.com/properties/123",
"pageTitle": "Beautiful 3BR Home",
"pageDuration": 45,
"pageReferrer": "https://google.com/search",
"ipAddress": "192.168.1.1",
"userAgent": "Mozilla/5.0..."
}'Property View Event
Track interest in specific properties (real estate):
curl -X POST https://api.suresend.ai/api/partner/events \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "property_view",
"personId": "person-uuid",
"message": "Viewed 3-bedroom home in Portland",
"property": {
"id": "prop-123",
"address": "123 Main St, Portland, OR 97201",
"price": 500000,
"beds": 3,
"baths": 2,
"sqft": 1800,
"propertyType": "house",
"listingUrl": "https://example.com/properties/123"
}
}'Saved Property Event
Track when contacts save properties to their favorites or saved lists:
curl -X POST https://api.suresend.ai/api/partner/events \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "property_save",
"personId": "person-uuid",
"message": "Saved property to favorites",
"description": "Saved 3-bedroom home to favorites list",
"property": {
"id": "prop-456",
"address": "456 Oak Ave, Portland, OR 97202",
"price": 650000,
"beds": 3,
"baths": 2.5,
"sqft": 2100,
"propertyType": "house",
"listingUrl": "https://example.com/properties/456",
"savedToList": "Favorites"
},
"metadata": {
"savedFrom": "search results",
"deviceType": "mobile",
"interestLevel": "very-high",
"action": "saved_to_favorites"
}
}'Property Data Fields:
savedToList- Name of the saved list (e.g., "Favorites", "Shortlist", "Must See")
Metadata Fields:
savedFrom- Where the property was saved from (e.g., "search results", "property detail page", "agent recommendation")deviceType- Device used to save the propertyinterestLevel- Indicates high engagement (e.g., "very-high")action- Specific action taken (e.g., "saved_to_favorites", "added_to_shortlist")
Property Search Event
Track search behavior and preferences:
curl -X POST https://api.suresend.ai/api/partner/events \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "property_search",
"personId": "person-uuid",
"message": "Searched for homes in Portland",
"propertySearch": {
"minPrice": 300000,
"maxPrice": 500000,
"beds": 3,
"baths": 2,
"location": "Portland, OR",
"propertyType": "house",
"keywords": "modern kitchen, garage"
}
}'Form Submission Event
Track form submissions with contact creation:
curl -X POST https://api.suresend.ai/api/partner/events \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "form_submission",
"person": {
"firstName": "John",
"lastName": "Doe",
"emails": [{"value": "[email protected]", "isPrimary": true}],
"phones": [{"value": "+1-555-1234", "isPrimary": true}]
},
"message": "Contact form submitted",
"description": "User requested more information about properties",
"metadata": {
"formName": "Contact Us",
"formId": "contact-form-1",
"interests": ["buying", "3-bedroom"],
"budget": "300k-500k"
}
}'Response includes person:
{
"event": { ... },
"person": {
"id": "person-uuid",
"firstName": "John",
"lastName": "Doe",
...
},
"wasPersonCreated": true, // true if a new person was created, false if existing
"wasCompanyCreated": false
}Email Campaign Event
Track email engagement:
curl -X POST https://api.suresend.ai/api/partner/events \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "email_opened",
"personId": "person-uuid",
"message": "Opened Spring 2025 campaign email",
"campaign": {
"name": "Spring 2025 Listings",
"id": "campaign-123",
"source": "mailchimp",
"medium": "email"
}
}'Person Matching
When creating events, you can associate them with people in three ways:
1. Use Existing Person ID
If you already know the person ID:
{
"type": "page_view",
"personId": "existing-person-uuid",
"pageUrl": "https://example.com/page"
}2. Match Existing Person by Email/Phone
Provide person data, and the system will find the matching contact:
{
"type": "page_view",
"person": {
"emails": [{"value": "[email protected]"}]
},
"pageUrl": "https://example.com/page"
}3. Create New Person Automatically
If no match is found, a new person is created:
{
"type": "form_submission",
"person": {
"firstName": "New",
"lastName": "Lead",
"emails": [{"value": "[email protected]", "isPrimary": true}],
"phones": [{"value": "+1-555-1234", "isPrimary": true}],
"tags": ["website-lead"]
},
"message": "New lead from website"
}Company Association
When creating events, you can optionally associate a company with the person. This is useful for:
- B2B lead tracking where contacts represent businesses
- Linking form submissions to company accounts
- Automatically associating people with their employers
- Tracking company-level engagement
How Company Association Works
- Find by ID: If you provide a company
id, the system looks up that specific company - Find by Name: If no ID, the system searches for an existing company by name (case-insensitive)
- Create if Not Found: If no match is found and name is provided, a new company is created
- Automatic Person Association: When a company is found or created, the person is automatically linked to that company
Company Parameter Structure
{
"company": {
"id": "uuid (optional - use to link to existing company)",
"name": "string (required if no id)",
"website": "string",
"industry": "string",
"companySize": "string",
"description": "string",
"annualRevenue": 5000000,
"registrationNumber": "string",
"taxId": "string",
"primaryEmail": "string",
"primaryPhone": "string",
"emails": [
{ "value": "[email protected]", "type": "work", "isPrimary": true }
],
"phones": [
{ "value": "+1-555-1234", "type": "main", "isPrimary": true }
],
"addresses": [
{
"street": "123 Business St",
"city": "San Francisco",
"state": "CA",
"code": "94105",
"country": "US",
"type": "headquarters",
"isPrimary": true
}
],
"tags": ["enterprise", "tech-sector"],
"customFields": {
"accountTier": "Gold"
}
}
}Response with Company
When a company is included in the request, the response includes the company object and a creation flag:
{
"person": { ... },
"event": { ... },
"company": {
"id": "company-uuid",
"name": "Acme Corporation",
"website": "https://acme.com",
"industry": "Technology",
"companySize": "51-200",
"description": "Leading provider of innovative solutions",
"emails": [
{ "value": "[email protected]", "type": "work", "isPrimary": true, "status": "active" }
],
"phones": [
{ "value": "+1-555-1234", "type": "main", "isPrimary": true, "status": "active" }
],
"addresses": [
{
"street": "123 Business St",
"city": "San Francisco",
"state": "CA",
"code": "94105",
"country": "US",
"type": "headquarters",
"isPrimary": true
}
],
"tags": ["enterprise"],
"created": "2025-10-22T12:00:00Z",
"updated": "2025-10-22T12:00:00Z"
},
"wasPersonCreated": true,
"wasCompanyCreated": true
}HTTP Status Codes
The response status code indicates what was created:
- 201 Created: A new person OR company was created
- 200 OK: Both person and company already existed (matched existing records)
Example: Form Submission with Company
curl -X POST https://api.suresend.ai/api/partner/events \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "form_submission",
"person": {
"firstName": "Jane",
"lastName": "Smith",
"emails": [{"value": "[email protected]", "isPrimary": true}],
"phones": [{"value": "+1-555-2345", "isPrimary": true}]
},
"company": {
"name": "Acme Corporation",
"website": "https://acmecorp.com",
"industry": "Technology",
"companySize": "51-200"
},
"message": "Demo request submitted",
"metadata": {
"formName": "Demo Request",
"productInterest": "Enterprise Plan"
}
}'Example: Link to Existing Company by ID
curl -X POST https://api.suresend.ai/api/partner/events \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "inquiry",
"person": {
"firstName": "Bob",
"lastName": "Johnson",
"emails": [{"value": "[email protected]"}]
},
"company": {
"id": "existing-company-uuid"
},
"message": "Sales inquiry from existing account"
}'Company Deduplication
The system uses case-insensitive name matching to prevent duplicate companies:
"Acme Corporation"matches"acme corporation"and"ACME CORPORATION"- Whitespace is trimmed from company names
- If a match is found, existing company data is updated (non-destructive merge)
- New emails, phones, addresses, and tags are added without removing existing ones
Person-Company Association
When both person and company are involved:
- A
PersonCompanyassociation is automatically created - If the company was newly created, it is set as the person's primary company
- If the association already exists, no duplicate is created
- The
is_currentflag is set totruefor new associations
Validation
The company parameter has the following requirements:
- Company name is required (unless providing an
idfor lookup) - If an
idis provided, the company must exist in your team - All other fields are optional
Event Data Structure
Core Fields
All events have these base fields:
{
"type": "page_view", // Required: Event type
"personId": "uuid", // Person ID (use personId OR person)
"person": {...}, // Person data (use personId OR person)
"company": {...}, // Company data (optional, see Company Association)
"occurredAt": "2025-10-16...", // When event occurred (ISO 8601)
"message": "Short summary", // Brief description
"description": "Long details", // Detailed description
"source": "website", // Event source
"system": "google_analytics" // Tracking system
}Page View Fields
{
"pageUrl": "https://example.com/page",
"pageTitle": "Page Title",
"pageDuration": 45, // Time on page (seconds)
"pageReferrer": "https://google.com",
"ipAddress": "192.168.1.1",
"userAgent": "Mozilla/5.0..."
}Property Data
{
"property": {
"id": "prop-123",
"address": "123 Main St",
"price": 500000,
"beds": 3,
"baths": 2,
"sqft": 1800,
"propertyType": "house",
"listingUrl": "https://...",
// Any custom fields
}
}Property Search Data
{
"propertySearch": {
"minPrice": 300000,
"maxPrice": 500000,
"beds": 3,
"baths": 2,
"location": "Portland, OR",
"propertyType": "house",
"keywords": "modern, garage"
}
}Campaign Data
{
"campaign": {
"name": "Spring 2025",
"id": "campaign-123",
"source": "facebook",
"medium": "cpc",
"term": "portland homes",
"content": "ad-variant-a"
}
}Custom Metadata
The metadata field allows you to store any additional data as flexible key-value pairs. This is perfect for:
- Form-specific data (form name, form ID, submission details)
- User preferences and interests
- Session/tracking information
- Budget/price preferences
- Custom attributes specific to your application
- Integration-specific data
Flexible Structure:
{
"metadata": {
"formName": "Contact Form",
"formId": "contact-form-1",
"deviceType": "mobile",
"sessionId": "session-123",
"interests": ["buying", "3-bedroom", "garage"],
"budget": "300k-500k",
"customField": "any value",
"nestedData": {
"key": "value",
"another": "nested value"
}
}
}Benefits:
- ✅ Store any JSON-serializable data
- ✅ No schema restrictions
- ✅ Automatically displayed in CRM activity timeline
- ✅ Indexed for efficient querying
- ✅ Supports nested objects and arrays
Limits:
To ensure system performance and prevent abuse, metadata has the following reasonable limits:
- Maximum Size: 256 KB (262,144 bytes)
- Plenty of space for detailed form data and tracking information
- Most metadata will be under 5 KB
- Maximum Keys: 100 keys at root level
- Sufficient for comprehensive event tracking
- Nested objects can have additional keys
- Maximum Depth: 10 levels of nesting
- Prevents overly complex structures
- More than enough for any legitimate use case
Example of Acceptable Metadata:
{
"formData": {
"personalInfo": {
"name": "John Doe",
"email": "[email protected]",
"preferences": {
"budget": "300k-500k",
"bedrooms": 3,
"locations": ["Charleston", "Mount Pleasant"]
}
}
},
"tracking": {
"referrer": "google.com",
"sessionId": "abc123",
"deviceType": "mobile"
},
"engagement": {
"timeOnPage": 120,
"scrollDepth": 85,
"clicks": ["cta-button", "property-card"]
}
}This example is well within limits (< 1 KB, 15 keys, 4 levels deep).
Matching Event Types
Every event carries two type fields, and they answer different questions.
| Field | What it is | Use it for |
|---|---|---|
eventType | The raw value exactly as stored, in whatever format the producer wrote it — Viewed Property, page_view, property_save can all appear in a single response | Displaying the original value, debugging, round-tripping data you wrote yourself |
canonicalEventType | The normalized Title Case value. All spellings of one concept collapse to one value | Matching, filtering, grouping, routing — this is the field to build on |
Events reach Sure Send from many producers — our tracking pixel, partner integrations, imports, and
your own API calls — and each writes the type its own way. canonicalEventType folds those together
so you never have to guess:
[
{ "eventType": "Viewed Property", "canonicalEventType": "Viewed Property" },
{ "eventType": "property_save", "canonicalEventType": "Saved Property" },
{ "eventType": "page_view", "canonicalEventType": "Viewed Page" }
]Canonical values:
Registration · Seller Inquiry · Property Inquiry · General Inquiry · Viewed Page ·
Viewed Property · Saved Property · Incoming Call · Visited Open House · Property Search ·
Saved Property Search · Form Submission
Normalization ignores case, delimiter, and word order, so property_view, Property View,
viewedProperty, VIEWED-PROPERTY and Viewed Property all canonicalize to Viewed Property.
Property views recorded by Lofty (stored as Browse) canonicalize to Viewed Property too.
Treat this vocabulary as open, not closedTypes outside the list above — including any custom type you send — pass through to
canonicalEventTypeunchanged rather than being dropped or nulled. The field is always present.
New canonical values may be added over time, so handle unrecognized values gracefully instead of
switching exhaustively.
eventType is a frozen contract: it returns the exact stored bytes and will never be retroactively
normalized, so integrations built against raw values keep working.
Retrieving Events
List All Events
Endpoint: GET /api/partner/events
curl "https://api.suresend.ai/api/partner/events?limit=50" \
-H "Authorization: Bearer YOUR_API_TOKEN"Query Parameters:
personId- Filter by personcanonicalEventType- Recommended. Filter by canonical event type; matches every stored
spelling of that type. Accepts a single value, a comma-separated list, or a repeated array
parameter (canonicalEventType[]=Viewed Page&canonicalEventType[]=Saved Property)eventType- Filter by the raw stored value. ⚠️ Exact, case-sensitive match — events stored
under a different spelling of the same concept will not matchsource- Filter by sourcelimit- Items per page (default: 25, max: 100)page- Page number (default: 1)
Response:
{
"events": [
{
"id": "event-uuid",
"personId": "person-uuid",
"eventType": "page_view",
"canonicalEventType": "Viewed Page",
"occurredAt": "2025-10-16T12:00:00Z",
"pageUrl": "https://example.com/page",
"metadata": {
"formName": "Contact Us",
"interests": ["buying"]
},
"created": "2025-10-16T12:00:00Z",
"updated": "2025-10-16T12:00:00Z"
}
],
"meta": {
"total_count": 137,
"current_page": 1,
"total_pages": 6,
"per_page": 25
}
}Note: The
metadatafield is included in responses when present on the event.
Filter Events by Person
curl "https://api.suresend.ai/api/partner/events?personId=person-uuid&limit=100" \
-H "Authorization: Bearer YOUR_API_TOKEN"Filter Events by Type
Filter on canonicalEventType to catch every stored spelling of the type in one call:
curl --get "https://api.suresend.ai/api/partner/events" \
--data-urlencode "canonicalEventType=Viewed Property" \
--data-urlencode "limit=100" \
-H "Authorization: Bearer YOUR_API_TOKEN"That single request returns events stored as Viewed Property, property_view, viewedProperty,
and Lofty Browse alike. Request several types at once with a comma-separated list:
curl --get "https://api.suresend.ai/api/partner/events" \
--data-urlencode "canonicalEventType=Viewed Property,Saved Property" \
-H "Authorization: Bearer YOUR_API_TOKEN"
Avoid?eventType=property_viewThe
eventTypefilter is an exact, case-sensitive match on the raw value. A team whose events are
stored asViewed Propertyreturns zero rows foreventType=property_view— with no error to tell
you the match failed. UsecanonicalEventTypeunless you specifically need raw-value matching.
Get Specific Event
Endpoint: GET /api/partner/events/{id}
curl https://api.suresend.ai/api/partner/events/event-uuid \
-H "Authorization: Bearer YOUR_API_TOKEN"Updating Events
Endpoint: PUT /api/partner/events/{id}
curl -X PUT https://api.suresend.ai/api/partner/events/event-uuid \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"message": "Updated description",
"metadata": {
"additionalField": "new value"
}
}'Deleting Events
Endpoint: DELETE /api/partner/events/{id}
curl -X DELETE https://api.suresend.ai/api/partner/events/event-uuid \
-H "Authorization: Bearer YOUR_API_TOKEN"Common Use Cases
1. Track Website Activity
// Website tracking script
async function trackPageView(email, pageUrl, pageTitle) {
await fetch('https://api.suresend.ai/api/partner/events', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'page_view',
person: {
emails: [{ value: email }]
},
pageUrl,
pageTitle,
pageDuration: Math.round((Date.now() - pageLoadTime) / 1000),
pageReferrer: document.referrer,
userAgent: navigator.userAgent
})
});
}2. Track Form Submissions
// Form handler
async function handleFormSubmit(formData) {
const response = await axios.post(
'https://api.suresend.ai/api/partner/events',
{
type: 'form_submission',
person: {
firstName: formData.firstName,
lastName: formData.lastName,
emails: [{ value: formData.email, isPrimary: true }],
phones: [{ value: formData.phone, type: 'mobile', isPrimary: true }]
},
message: 'Contact form submitted',
metadata: {
formName: 'Contact Us',
interests: formData.interests,
message: formData.message
},
source: 'website',
pageUrl: window.location.href
},
{
headers: { Authorization: `Bearer ${API_TOKEN}` }
}
);
const { person, wasPersonCreated } = response.data;
console.log(wasPersonCreated ? 'New lead created' : 'Existing contact updated');
}3. Build Engagement Timeline
async function getContactTimeline(personId) {
const response = await axios.get(
`https://api.suresend.ai/api/partner/events`,
{
params: { personId, limit: 100 },
headers: { Authorization: `Bearer ${API_TOKEN}` }
}
);
// Group by canonical type — grouping on the raw `eventType` would split the
// same concept across several buckets ("Viewed Page" vs "page_view").
const timeline = response.data.events.reduce((acc, event) => {
const type = event.canonicalEventType;
if (!acc[type]) acc[type] = [];
acc[type].push(event);
return acc;
}, {});
return timeline;
}
// Usage
const timeline = await getContactTimeline('person-uuid');
console.log(`Page views: ${timeline['Viewed Page']?.length || 0}`);
console.log(`Property views: ${timeline['Viewed Property']?.length || 0}`);
console.log(`Searches: ${timeline['Property Search']?.length || 0}`);4. Track Property Interest
async function trackPropertyView(personId, property) {
await axios.post(
'https://api.suresend.ai/api/partner/events',
{
type: 'property_view',
personId,
message: `Viewed ${property.beds}BR home at ${property.address}`,
property: {
id: property.id,
address: property.address,
price: property.price,
beds: property.beds,
baths: property.baths,
sqft: property.sqft,
listingUrl: property.url
},
pageUrl: window.location.href,
source: 'website'
},
{
headers: { Authorization: `Bearer ${API_TOKEN}` }
}
);
// Auto-tag based on price range
if (property.price > 1000000) {
await applyTag(personId, 'luxury-buyer');
}
}
// Track when properties are saved to favorites
async function trackSavedProperty(personId, property, savedFrom) {
await axios.post(
'https://api.suresend.ai/api/partner/events',
{
type: 'property_save',
personId,
message: `Saved property to favorites`,
description: `Saved ${property.beds}BR home at ${property.address}`,
property: {
id: property.id,
address: property.address,
price: property.price,
beds: property.beds,
baths: property.baths,
sqft: property.sqft,
propertyType: property.type,
listingUrl: property.url,
savedToList: 'Favorites'
},
metadata: {
savedFrom: savedFrom, // 'search results', 'property detail page', etc.
deviceType: /mobile/i.test(navigator.userAgent) ? 'mobile' : 'desktop',
interestLevel: 'very-high',
action: 'saved_to_favorites'
},
source: 'website'
},
{
headers: { Authorization: `Bearer ${API_TOKEN}` }
}
);
// Auto-tag high-intent leads
await applyTag(personId, 'high-intent-buyer');
}5. Analyze Search Behavior
async function analyzeSearchBehavior(personId) {
const response = await axios.get(
`https://api.suresend.ai/api/partner/events`,
{
params: { personId, canonicalEventType: 'Property Search', limit: 100 },
headers: { Authorization: `Bearer ${API_TOKEN}` }
}
);
const searches = response.data.events;
// Analyze price range preferences
const priceRanges = searches.map(s => ({
min: s.propertySearch?.minPrice || 0,
max: s.propertySearch?.maxPrice || Infinity
}));
const avgMinPrice = priceRanges.reduce((sum, r) => sum + r.min, 0) / priceRanges.length;
const avgMaxPrice = priceRanges.reduce((sum, r) => sum + r.max, 0) / priceRanges.length;
// Analyze location preferences
const locations = searches
.map(s => s.propertySearch?.location)
.filter(Boolean);
const topLocations = [...new Set(locations)]
.map(loc => ({
location: loc,
count: locations.filter(l => l === loc).length
}))
.sort((a, b) => b.count - a.count);
return {
averagePriceRange: { min: avgMinPrice, max: avgMaxPrice },
topLocations,
totalSearches: searches.length
};
}6. Custom Metadata Tracking
Track rich form data and user preferences:
async function trackContactFormSubmission(formData) {
await axios.post(
'https://api.suresend.ai/api/partner/events',
{
type: 'form_submission',
person: {
firstName: formData.firstName,
lastName: formData.lastName,
emails: [{ value: formData.email, isPrimary: true }],
phones: [{ value: formData.phone, isPrimary: true }]
},
message: `${formData.formName} submission`,
description: formData.message,
metadata: {
// Form identification
formName: formData.formName,
formId: formData.formId,
formVersion: '2.0',
// User preferences
interests: formData.interests, // ['buying', '3-bedroom', 'garage']
budget: formData.budget, // '300k-500k'
timeframe: formData.timeframe, // 'within-3-months'
// Additional context
pageUrl: window.location.href,
referringSource: document.referrer,
sessionId: getSessionId(),
deviceType: isMobile() ? 'mobile' : 'desktop',
// Custom business data
marketingConsent: formData.marketingConsent,
preferredContactMethod: formData.preferredContactMethod,
bestTimeToCall: formData.bestTimeToCall
},
source: 'website',
occurredAt: new Date().toISOString()
},
{
headers: { Authorization: `Bearer ${API_TOKEN}` }
}
);
}Benefits:
- All form data is preserved in the event
- Displays nicely in the CRM activity timeline
- Can be used for filtering and reporting
- No schema limitations
7. Email Campaign Tracking
// Track email opens
async function trackEmailOpen(personId, campaignId) {
await axios.post(
'https://api.suresend.ai/api/partner/events',
{
type: 'email_opened',
personId,
message: 'Opened marketing email',
campaign: {
id: campaignId,
name: 'Spring 2025 Campaign',
source: 'mailchimp',
medium: 'email'
},
source: 'email_campaign'
},
{
headers: { Authorization: `Bearer ${API_TOKEN}` }
}
);
}
// Track link clicks
async function trackEmailClick(personId, campaignId, linkUrl) {
await axios.post(
'https://api.suresend.ai/api/partner/events',
{
type: 'email_clicked',
personId,
message: `Clicked link: ${linkUrl}`,
campaign: {
id: campaignId,
name: 'Spring 2025 Campaign'
},
pageUrl: linkUrl,
source: 'email_campaign'
},
{
headers: { Authorization: `Bearer ${API_TOKEN}` }
}
);
}8. B2B Lead Tracking with Company
Track form submissions from business contacts with company information:
async function trackB2BFormSubmission(formData) {
const response = await axios.post(
'https://api.suresend.ai/api/partner/events',
{
type: 'form_submission',
person: {
firstName: formData.firstName,
lastName: formData.lastName,
emails: [{ value: formData.email, isPrimary: true }],
phones: [{ value: formData.phone, isPrimary: true }]
},
company: {
name: formData.companyName,
website: formData.companyWebsite,
industry: formData.industry,
companySize: formData.companySize
},
message: `B2B inquiry from ${formData.companyName}`,
metadata: {
formName: 'B2B Contact Form',
productInterest: formData.productInterest,
estimatedDealSize: formData.budget
},
source: 'website'
},
{
headers: { Authorization: `Bearer ${API_TOKEN}` }
}
);
const { person, company, wasPersonCreated, wasCompanyCreated } = response.data;
if (wasCompanyCreated) {
console.log(`New company created: ${company.name}`);
} else {
console.log(`Linked to existing company: ${company.name}`);
}
if (wasPersonCreated) {
console.log(`New contact created: ${person.firstName} ${person.lastName}`);
}
}Rate Limiting
The Events API has specific rate limits to handle high-volume tracking:
Rate Limit Headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 85
X-RateLimit-Reset: 3600
X-RateLimit-Context: token:POST.events
Best Practices:
- Batch events when possible
- Monitor rate limit headers
- Implement exponential backoff on 429 errors
- Contact support for higher limits if needed
Best Practices
Event Naming
- Use standard types when one fits - Sending a type from the Common Event Types
table means it canonicalizes to a known value, so every consumer of your data can match it - Descriptive custom types - Use clear names like
property_inquirynotpi - Pick one format and stay with it - The format you send is stored verbatim, so mixing
page_viewandViewed Pagein your own writes leaves both spellings in the raweventType
field forever. Any consistent format works;page_viewstyle is the common choice
Reading Event Types
- Match on
canonicalEventType- Never branch on the raweventType, which varies by producer - Filter with
?canonicalEventType=- TheeventTypefilter is exact and case-sensitive, and
returns zero rows rather than an error when the spelling doesn't match - Handle unknown values - The canonical vocabulary can grow, and custom types pass through
as-is. Prefer a default branch over an exhaustive switch
Data Quality
- Always include occurredAt - Track when events actually happened
- Provide meaningful messages - Help humans understand the event
- Use structured data - Leverage property, propertySearch, campaign fields
- Include source/system - Track where events originate
Performance
- Async tracking - Don't block user experience waiting for events
- Batch when possible - Reduce API calls
- Cache person IDs - Avoid repeated person lookups
- Handle errors gracefully - Don't fail user actions if tracking fails
Privacy
- Respect user consent - Only track if user consents
- Anonymize when needed - Don't track PII unnecessarily
- Follow regulations - GDPR, CCPA compliance
- Provide opt-out - Allow users to stop tracking
Integration Examples
Google Analytics Integration
// Track GA events in CRM
function setupGAIntegration() {
gtag('event', 'page_view', {
event_callback: async function(url) {
if (window.userEmail) {
await trackPageView(window.userEmail, url, document.title);
}
}
});
}Segment Integration
// Forward Segment events to CRM
analytics.ready(() => {
analytics.on('track', async (event, properties) => {
const user = analytics.user();
await axios.post(
'https://api.suresend.ai/api/partner/events',
{
type: event.toLowerCase().replace(/ /g, '_'),
person: {
emails: [{ value: user.email() }]
},
message: event,
metadata: properties,
source: 'segment',
system: 'segment'
},
{
headers: { Authorization: `Bearer ${API_TOKEN}` }
}
);
});
});Next Steps
- Set up Webhooks to receive event notifications
- Learn about People API for contact management
- Check Getting Started for authentication
- Explore Custom Fields for extended data
Need Help?
- API Reference: See full Events endpoints in sidebar
- Support: [email protected]
- Examples: Check our GitHub examples (coming soon)
Updated about 1 month ago
