PostHog data pipeline to Accoil
Set up a reliable and fully supported PostHog-to-Accoil event pipeline using a custom HTTP destination.
Limitations of 'Accoil Analytics' data pipelinePostHog has released a built-in destination labeled “Accoil Analytics”, but this was created without direct consultation with our team. While well-intentioned, it has some limitations and may not align with our ingestion API structure.
We recommend using a custom HTTP Destination instead.
This approach:
✅ Is tested and working with multiple Accoil customers
✅ Leverages PostHog’s flexible HTTP destination setup
✅ Ensures full compatibility with Accoil’s ingestion API
⚙️ Setup Steps
-
In PostHog, go to:
Data Pipelines → + New Destination → HTTP Webhook -
Webhook URL:
Enter any valid URL (e.g.https://accoil.com
). This is required by PostHog, but not used by the function. -
Function Code:
Use the code below -
Add Your Accoil API Key:
– Find it in Accoil > Account > General Settings -
(Optional):
- Set
"debug": true
during setup for easier testing - Apply event filters to limit data during initial testing
- Set
-
Click “Test function” to confirm a successful response
-
Set
debug
back tofalse
and Save
✅ That’s it! Your PostHog events are now flowing into Accoil.
We're working on a improving the Accoil destination for PostHog to simplify this further — stay tuned!
🧠 Function Code (Paste into Webhook)
let api_key := 'YOUR_ACCOIL_API_KEY_HERE'
let debug := false
let endpoint := 'events'
if (event.event == '$identify' or event.event == '$set') {
endpoint := 'users'
} else if (event.event == '$group_identify' or event.event == '$group' or event.event == '$groupidentify') {
endpoint := 'groups'
}
let accoil_payload := {}
accoil_payload.api_key := api_key
accoil_payload.timestamp := toUnixTimestamp(event.timestamp) * 1000
if (endpoint == 'users') {
accoil_payload.user_id := event.distinct_id
if (not empty(event.properties.$group_id)) {
accoil_payload.group_id := event.properties.$group_id
}
let traits := {}
// First check for properties in the $set object (standard location for identify events)
if (not empty(event.properties.$set)) {
for (let key, value in event.properties.$set) {
if (not empty(value) and not key like '$%') {
traits[key] := value
}
}
}
// Still include person properties for completeness
if (not empty(person.properties.email) and empty(traits.email)) traits.email := person.properties.email
if (not empty(person.properties.name) and empty(traits.name)) traits.name := person.properties.name
if (not empty(person.properties.created_at) and empty(traits.created_at)) traits.created_at := person.properties.created_at
if (not empty(person.properties.role) and empty(traits.role)) traits.role := person.properties.role
for (let key, value in person.properties) {
// Check if the key is already in traits
let key_in_traits := not empty(traits[key])
if (not empty(value) and not key like '$%' and not key_in_traits
and key != 'email' and key != 'name' and key != 'created_at' and key != 'role') {
traits[key] := value
}
}
accoil_payload.traits := traits
} else if (endpoint == 'groups') {
// Handle the actual format of PostHog group events
if (not empty(event.properties.$group_key)) {
accoil_payload.group_id := event.properties.$group_key
} else if (not empty(event.properties.$group_id)) {
accoil_payload.group_id := event.properties.$group_id
} else if (not empty(event.properties.group_id)) {
accoil_payload.group_id := event.properties.group_id
} else {
throw Error('Group ID is required for group identification')
}
if (not empty(event.distinct_id)) {
accoil_payload.user_id := event.distinct_id
}
let traits := {}
// First check in the $group_set object which is the standard location
if (not empty(event.properties.$group_set)) {
// Copy all properties from $group_set to traits
for (let key, value in event.properties.$group_set) {
if (not empty(value)) {
traits[key] := value
}
}
} else {
// Fallback to legacy format
if (not empty(event.properties.$group_name)) traits.name := event.properties.$group_name
else if (not empty(event.properties.name)) traits.name := event.properties.name
if (not empty(event.properties.$group_created_at)) traits.created_at := event.properties.$group_created_at
else if (not empty(event.properties.created_at)) traits.created_at := event.properties.created_at
// Include other properties not starting with $ and not already handled
for (let key, value in event.properties) {
let is_system_key := key like '$%'
let is_special_key := key == 'group_id' or key == '$group_id' or key == 'name' or
key == '$group_name' or key == 'created_at' or key == '$group_created_at'
if (not empty(value) and not is_system_key and not is_special_key) {
traits[key] := value
}
}
}
accoil_payload.traits := traits
} else {
accoil_payload.user_id := event.distinct_id
if (event.event == '$autocapture') {
let element_type := event.properties.$element_type ?? 'element'
let action := event.properties.$event_type ?? 'interacted'
accoil_payload.event := f'{element_type}_{action}'
} else if (event.event == '$pageview') {
accoil_payload.event := 'Page_Viewed'
} else {
accoil_payload.event := event.event
if (not event.event like '%_%') {
print(f'Event name "{event.event}" might not follow Accoil\'s Noun-Verb format. Consider renaming to "Noun_Verb" format.')
}
}
}
let webhook_url := f'https://in.accoil.com/v1/{endpoint}'
if (debug) {
print('Sending to Accoil:', webhook_url)
print('Payload:', accoil_payload)
}
let res := fetch(webhook_url, {
'method': 'POST',
'headers': {
'Content-Type': 'application/json'
},
'body': accoil_payload
})
if (res.status >= 400) {
throw Error(f'Error from Accoil API (status {res.status}): {res.body}')
}
if (debug) {
print('Response:', res.status, res.body)
}
Updated 3 days ago