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 pipeline

PostHog 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

  1. In PostHog, go to:
    Data Pipelines → + New Destination → HTTP Webhook

  2. Webhook URL:
    Enter any valid URL (e.g. https://accoil.com). This is required by PostHog, but not used by the function.

  3. Function Code:
    Use the code below

  4. Add Your Accoil API Key:
    – Find it in Accoil > Account > General Settings

  5. (Optional):

    • Set "debug": true during setup for easier testing
    • Apply event filters to limit data during initial testing
  6. Click “Test function” to confirm a successful response

  7. Set debug back to false 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)
}