Custom events
13 min
a custom event is any event chord receives that is not part of the chord tracking plan — either an event whose name is not one of the canonical events ( order completed , product viewed , checkout started , product added , …) or a canonical event carrying non standard properties this document explains what happens to those events how they are processed, what transformation (if any) is applied, and which destinations forward them the short answer chord does not validate events against the tracking plan, and it does not reject, quarantine, or strip events for being non compliant every event that is ingested flows through the same pipeline custom events are processed as is , with only the minimal, optional transformations described below, and are forwarded to destinations what a custom event ultimately looks like at a destination depends entirely on that destination, because each destination decides how to map an event to its own schema some forward custom events verbatim; some only act on a fixed set of canonical events and silently ignore everything else the tracking plan is a convention, not a schema validator it defines a set of canonical event names and snake case field names ( email , phone , first name , address1 , zipcode , …) destination code uses these canonical names as fallback lookups when extracting properties there is no component anywhere in ingest, rotor, or the destination functions that checks an incoming event against the plan and blocks it for failing to match how an event is processed every event — canonical or custom — travels the same path chord sdk / s2s → ingest → kafka → rotor (function chain) → destination inside rotor, the function chain applies a series of transformations none of these steps inspect whether an event complies with the tracking plan they treat a custom event exactly as they treat a canonical one step what it does applies to custom events? geo enrichment resolves context ip to context geo (browser events only) yes, identically user recognition merges anonymous events with known identity (if configured) yes, identically connection filters drop/allow rules matching on event type, name, or property values yes — can drop a custom event if a rule names it, but not because it is custom property mappings customer defined field remapping ( @jitsu/mapping engine ) yes, identically consent filtering drops events lacking required consent categories yes, identically deduplication skips repeat messages within a redis backed window (if enabled) yes, identically udfs custom javascript transforms that can modify, drop, or fan out events yes, identically destination function maps the event to the destination's api/schema destination specific — see below the only places an event is ever dropped are a connection filter rule, consent enforcement, a udf returning null / "drop" , deduplication, or a destination deliberately not firing a custom event is never dropped merely for being absent from the tracking plan how destinations handle custom events this is where behavior diverges there are two delivery modes, and within each, destinations fall into a few distinct patterns cloud mode destinations (server side) these run as functions in rotor the dominant pattern is passthrough the event name and properties are forwarded with little or no transformation, so custom events arrive intact pattern behavior for custom events examples passthrough event forwarded as is; the event name is used directly webhook, segment, mongodb, data warehouse / bulker, june, intercom, heap, mixpanel, posthog canonical mapping + fallback canonical events are translated to the destination's standard event; unmapped (custom) events still go through via a default fallback ga4 (custom name is handelized/kebab cased, standard props filtered), facebook conversions (custom name passed through unchanged) configurable mapping the destination acts only on events the customer has explicitly mapped amplitude (supports a $drop mapping), braze (purchase event list, otherwise generic track) identity / fixed purpose built around identify / group or a small set of events; track style custom events get generic treatment or none klaviyo (special cases order completed , all other tracks become generic metrics), hubspot, salesforce data warehouse destinations are the most permissive a custom track event simply gets its own table named after the event (e g my custom event ), or its raw document inserted (mongodb) even canonical mapping destinations like ga4 and facebook do not drop custom events — they fall through to a default branch that still sends the event under its own (optionally normalized) name device mode destinations (client side) device mode plugins ( libs/jitsu js/src/destination plugins/ ) fire directly from the browser via each vendor's pixel/sdk per the forwarding policy above, every plugin handles the unrecognized event case explicitly — it either forwards the event or logs an explicit skip pattern behavior for custom events examples passthrough an unrecognized event name is forwarded to the vendor sdk under its own name (or as a generic "custom event") facebook pixel ( tracksinglecustom ), tiktok pixel, pinterest tag, insider pixel, bing ads, stackadapt pixel, dotdigital tag ( event() ), google tag manager (pushed to the data layer as event "\<event name>" ; triggers and tags decide what happens next) fixed taxonomy (explicit skip) only the platform's recognized events fire; unrecognized events are skipped with a console warn snap pixel, northbeam pixel, reddit pixel, retention com, postie, spotify ad analytics pixel, openai ads pixel and conversions api, nosto tag and nosto config driven fires only for events the customer mapped in the plugin's configuration; everything else (including unmapped canonical events) is skipped twitter ads ( eventmapping ), google ads ( clickconversions / pageloadconversions ) if you need to send high cardinality or custom named events to an ad platform, choose a passthrough plugin fixed taxonomy plugins (snap, reddit, spotify, etc ) are scoped to the conversion events the vendor's backend recognizes; they will skip anything outside that set and log a warning so the skip is visible custom property mappings custom property mappings are a separate mechanism from custom event names when a customer maps a property in hub (e g properties customer type → customer type ), the dispatcher writes it as a top level key on the event a plugin only carries it into the outgoing payload if the plugin spreads getcustommappedproperties / getcustomdataproperties — in which case the custom property reaches the destination even on a fixed event set not every plugin does this plugins that build a hardcoded payload from formatters ( formatorderdata , etc ) without that spread silently drop custom mapped properties today the plugins that forward them are facebook pixel , snap pixel , tiktok pixel , pinterest tag , twitter ads , bing ads , google ads , reddit pixel , and dotdigital tag (on its custom event call only — see below); fixed taxonomy plugins such as spotify ad analytics pixel , postie , retention , northbeam pixel , sfmc collect , thetradedesk pixel , and yahoo pixel do not (pii fields like email and phone are excluded from this passthrough regardless ) forwarding is not the same as being used reddit pixel forwards custom properties and reddit accepts them without error, but reddit publishes a fixed metadata list ( conversion id , currency , item count , value , products ) with no general purpose container, and does not document what becomes of anything else so the property reaches the vendor and may still be inert where a vendor neither documents a passthrough field nor rejects unknown keys, say so in the destination doc rather than implying the mapping works — an operator who builds reporting on a silently discarded property is worse off than one who was told nothing a closed vendor schema can make forwarding impossible, not merely absent openai ads pixel is the first plugin in this category openai validates the event against a fixed schema — the conversion data accepts only type , amount , currency , contents and plan id , and there is no custom data slot like meta's or tiktok's an unknown key is not ignored the sdk logs validation failed; event dropped and the conversion never leaves the browser so for that destination the convention inverts it exists to stop custom properties being silently lost; here, forwarding them silently loses the entire event, which is strictly worse the plugin therefore sends none, and its custom property mapping test ts asserts the exclusion so the exception reads as a decision rather than an oversight a closed vendor api can make forwarding impossible, not merely absent nosto tag is the second plugin in this category and the clearer case nosto's browser api has no property bag at all each method takes a fixed signature, and its cart, customer and order objects are closed shapes — there is no custom data like meta's, no properties like tiktok's, and no key nosto would read a custom property has nowhere to go on either nosto surface, so both skip it and both public docs say so rather than leaving an operator to discover it a vendor can have an extensibility field on some calls and none on others dotdigital tag is the case that does not fit either bucket cleanly dotdigital's event() takes an arbitrary attribute bag, so custom properties are forwarded there — but every other method it exposes ( identify , productbrowse , productlist , cartupdate , checkout , purchasecomplete ) takes a fixed signature with no property bag, so they get none the plugin therefore spreads on exactly one call, and its custom property mapping test ts asserts both halves that mapped properties arrive on the custom event path, and that they cannot appear on a commerce payload where dotdigital defines the field names itself the public doc states the split, because an operator configuring a mapping needs to know which events it reaches before adding the usual spread to a new plugin, check whether the vendor has an extensibility field at all — and check it per call, not per vendor if it does not, omit the spread and pin that with a test custom event forwarding policy across destinations, the behaviors above are not all intentional — some are genuine vendor constraints and some are implementation gaps to keep current and future destinations consistent, chord follows one rule every destination must handle the unrecognized event case explicitly it either forwards the event or logs an explicit skip — it must never fall through silently a silent fall through (a switch with no default ) is the thing to avoid it drops events with no signal in the logs, so a custom event that never arrives at a destination looks identical to one that was delivered that is the worst outcome for debugging two buckets each destination belongs to one of two buckets, chosen by what the receiving platform actually does with an unrecognized event — not by whether the event can be transmitted bucket default for unrecognized events why passthrough capable — general purpose collectors and platforms with a real custom event api forward the event under its own name (with custom mapped properties) the platform stores or acts on arbitrary events, so forwarding is useful fixed taxonomy — attribution pixels whose backend only recognizes a closed set of events skip with an explicit console warn the platform discards or rejects unrecognized events, so forwarding would be a no op that falsely reads as "delivered" examples of passthrough capable webhook, data warehouse / bulker, mongodb, mixpanel, posthog, ga4, facebook (pixel via tracksinglecustom / conversions api via a custom event name ), tiktok, pinterest, insider, bing, dotdigital (the tag's event() and the v3 events import both create an insight data collection for any non reserved name) examples of fixed taxonomy snap (closed enum + five custom event n slots), spotify ad analytics ( view / purchase / lead only), postie (snowplow schemas it has registered), retention com (fixed case sensitive "reclaim" names), northbeam (custom goals must be pre registered vendor side), nosto (a closed set of page types plus cart, customer and order updates, with no custom event at all), reddit (eight standard events; a custom event is discarded unless its name is registered in the advertiser's reddit account), openai ads (closed enum plus a custom type whose name must be registered in ads manager) some fixed taxonomy platforms offer a registered custom event as an escape hatch, which is fixed taxonomy and config driven at once openai ads is the clearest example it accepts a custom event type, but only for names the advertiser has already created in ads manager, and it silently discards any other name so the destination skips an unmapped event by default, and forwards it only when the operator has supplied a custom event mapping entry — an explicit statement that the name exists vendor side forwarding on a guess would look like delivery and be a no op reddit has the same custom escape hatch vendor side, but chord's reddit destinations expose no event mapping credential, so they always skip an unrecognized event rather than guess at a registered name decide by platform, not by transport a destination's bucket is a property of the platform , and the pixel (device mode) and conversions api (server side) implementations for the same platform must make the same choice server side / capi delivery is permissive by construction — event name is just a string field in an http body, with no client sdk enum to satisfy — so it is tempting to forward everything server side but transmitting an event the platform ignores is still a no op; it just happens over http instead of a js call facebook accepts custom events on both its pixel and its conversions api, so both forward a fixed taxonomy platform like snap would skip on both there is no case where the capi side should forward what the pixel side drops for the same platform openai ads shows why this is not merely tidy its two surfaces deduplicate on (pixel id, event name, event id), so they must agree on the name as well as the decision to send if the pixel skipped an event that the conversions api forwarded under a name of its own, the conversion would be recorded once under a name that matches no conversion goal — worse than dropping it, because it reads as delivered both surfaces therefore resolve event names through one shared table and honor the same custom event mapping why custom mapped properties are always forwarded regardless of bucket the bucket decision is about the event name custom properties mapped by the customer are merged into every outgoing call (see below), because a fixed set platform still accepts extra fields on the events it does recognize practical guidance sending a custom event to a warehouse or a passthrough destination? it will arrive with its original name and properties no tracking plan compliance is required sending a custom event to an ad platform via a device mode pixel? confirm the plugin is passthrough or that you have mapped the event in the plugin's configuration fixed taxonomy plugins will skip it and log a console warn want a custom event to look like a canonical one at a destination? use a property mapping or a udf in the connection to reshape it before it reaches the destination function want to stop a custom event from reaching a destination? add a connection filter rule, or drop it in a udf — there is no automatic rejection to rely on summary chord is a flexible, schema less pipeline it accepts any well formed event, transforms it only as explicitly configured, and forwards it the tracking plan provides canonical names that destinations prefer when present, but compliance is never enforced custom events are first class citizens of the pipeline; the only meaningful variation is how individual destinations choose to map — or ignore — events they were not built to recognize