Any way to suppress contact.propertyChange events that fire on contact creation?

I am building an automation on top of the HubSpot CRM and subscribing to webhooks for contact creation and contact property changes.

The issue: when a new contact is created, I receive the contact.creation event and also a set of contact.propertyChange events for every property populated at creation. One new contact therefore triggers my pipeline multiple times, and I end up doing duplicate work.

What I was expecting:

- contact.created fires when the contact is created

- contact.propertyChange fires only when a property is genuinely updated afterwards

What I have tried so far:

- I could not find any subscription setting that suppresses property change events at creation time. If one exists and I have missed it, please point me to it.

- I considered comparing the event timestamp against the contact’s createdate and discarding property changes that fall within a small window of creation. It works, but a time based buffer feels fragile, especially with delayed or out of order webhook delivery.

- I also considered deduplicating on my side by caching the occuredAt and objectId as compound unique and then discarding the propertyChange events if that comes with the same compound key. This works well if the contact.created and contact.propertChange is in the same webhook payload other wise this modal doesnt works.

So my question is whether anyone has found a cleaner signal to distinguish creation time property changes from genuine later updates, or whether the timestamp comparison is simply the accepted approach here.

Any experience from people running this in production would be much appreciated.

I’ve run into this pattern with webhook-driven integrations as well. I wouldn’t rely on event ordering or assume that contact creation will always arrive before the related property Change events, since webhook delivery can be asynchronous.

A more reliable approach is to treat the webhook as a trigger rather than the source of truth. When a contact creation or property Change event arrives, use the object ID to fetch the current contact state and let your processing layer decide whether the change requires work.

For creation-time property changes, you can also maintain a short-lived “initializing” state for the object ID after receiving the creation event, but I’d avoid using a fixed timestamp window as the primary signal.

If HubSpot doesn’t expose a subscription-level flag distinguishing creation-time property population from subsequent updates, application-side state/idempotency is probably the safer production design than relying on timestamps or webhook ordering.

This is expected with HubSpot webhooks, as initial property values can also trigger propertyChange events. I’d avoid a fixed time window and instead make your processing idempotent while tracking the contact’s creation state to handle delayed or out-of-order events safely.

@frankhayes @Davidwarner432 thanks both, this is helpful.

Some context on my setup: I have independent logic for creation and for propertyChange, and they intentionally do different things. Creation triggers a full enrichment cycle. A propertyChange pulls only the specific property that changed, which keeps enrichment cost down and avoids reprocessing fields that have not moved. So the fetch-the-current-state-and-decide approach would mean losing that narrowness, which is why I was trying to solve it upstream instead.

The per-event counts matter too. Creation-time property events inflate my update counts and misrepresent how much genuine change is happening in the CRM.

@frankhayes the short-lived initializing state per object ID is interesting though, because that sits at the ingest layer rather than inside either handler. I could drop creation-time property events on arrival and leave both pipelines untouched. I am still thinking through this idea

Your instinct about the time-based buffer is right, it’s fragile, and it’ll bite you on redelivery. But there’s a cleaner signal, and there’s a config option you may have missed.

The config option first: you can subscribe to contact.propertyChange per property rather than for all properties. In the app’s webhook subscriptions, each propertyChange subscription targets a named property. If your pipeline only cares about a handful of fields, subscribing to those specifically removes most of the creation-time noise at source, because you’ll only hear about the ones you named. It doesn’t eliminate the overlap with creation, but it turns “every property populated at creation” into “the two or three I actually care about.”

For the remaining overlap, don’t compare occurredAt against wall clock, compare it against the record’s own createdate. That’s an equality check against a stored value rather than a tolerance window, so redelivery and out-of-order arrival don’t break it: a propertyChange whose occurredAt equals the contact’s createdate was part of the creation, whenever it happens to reach you. Fetch createdate once when you handle contact.creation and cache it with the object.

The structural fix, though, is to stop trying to suppress events at all. Make the consumer idempotent on the outcome rather than on the event. Key your work by (objectId, whatever-you-derived) and write it as an upsert. Then it genuinely doesn’t matter whether you get one event or nine for the same contact, the ninth is a no-op. Your compound-key idea is nearly this already; it only fails because you keyed on the event rather than on the result.

That also covers the cases the timestamp approach won’t: HubSpot batches webhooks and can redeliver on your endpoint timing out, so you should expect duplicates independently of the creation problem.

Hi @AdityaKumarSinha and Happy Monday!

Thanks for reaching out to the HubSpot Community and thanks everybody for your help!

I checked the webhooks documentation, and there’s no built-in subscription setting to suppress propertyChange events that fire during creation. A few things that may help are:

  1. changeSource field: every event payload includes a changeSource field (e.g. CRM_UI, CRM, API, etc., matching contact property history sources). This won’t distinguish creation-time vs. later changes on its own, but it’s worth checking whether your creation flow consistently produces a distinct changeSource/sourceId compared to genuine later edits from your specific integration.

  2. eventId and subscriptionId: these are included per event but eventId is explicitly documented as “not guaranteed to be unique,” so it can’t be relied on for dedup.

  3. No documented ordering/batching guarantee tying creation and propertyChange events into one atomic payload: the docs don’t state that these are guaranteed to arrive together or in a fixed order, which is what you’ve observed too.

This is on the legacy webhooks system, if you’re starting fresh, the newer webhooks-journal subscriptions API lets you subscribe to CREATE and UPDATE actions with specific property filters per subscription, though it doesn’t document creation-time suppression either.

Here are some resources:

Hey @Anton and @nickdeckerdevs1 do you have additional suggestions to help @AdityaKumarSinha, please?

Thanks so much and have a wonderful day!
Bérangère

This post was created with the assistance of AI tools

Hi @AdityaKumarSinha

This is simply how HubSpot operates. There is no hidden setting to disable these behaviors, so you have not missed anything.

The cleanest approach is to use only the contact.creation event to trigger the “new contact” logic. When this event fires, we can fetch the complete contact record directly from the API and ignore any accompanying propertyChange events.

To identify creation-related propertyChange events, we should rely on HubSpot’s event batching rather than comparing timestamps, which can be unreliable due to retries or out-of-order arrivals. If a propertyChange event appears in the same batch or shares the same occurredAt timestamp as a contact.creation event for the same contact, we can treat it as part of the creation process and skip it. Any propertyChange event arriving later on its own should be treated as a genuine contact update.

This means we do not need to depend on the contact.creation and propertyChange events sharing a compound key or being delivered in the same payload. Instead, the creation event serves as the source of truth for new contacts, while property changes are handled separately for subsequent updates.

Thanks!

Thanks for the suggestions and the documents, @Pietro87 , @Berangere , and @GRajput .

After running some trials with HubSpot, I have come to a conclusion similar to what @Pietro87 mentioned.

At the entry point, I check whether the contact.created event and the propertyChange event have the same objectId and occurredAt timestamp. If they do, I eliminate the propertyChange event.

This still leaves the possibility of the contact creation event and the propertyChange event arriving in different HubSpot webhook batches or payloads. For those cases, I fetch the contact and compare the contact’s createdate with the occurredAt timestamp of the propertyChange event.

One thing I noticed during testing is that the timestamps are not always exactly the same. I observed a difference of up to 3 ms, so I have added a safe buffer of 5 ms. If:

propertyChange occurredAt - createdate <= 5 ms

the propertyChange request is ignored by the system.

I am continuously monitoring this, and so far it seems to be working well.

Since I need to measure contact creation and property changes independently, I needed a reliable way to clearly separate and ignore the property change events generated as part of contact creation. For now, this approach seems to be working.

@Bérangère, is there anything new regarding the webhooks-journal subscription that could help resolve this problem? Any heads-up would be really useful. I will also go through the documentation in more detail.

Hi @AdityaKumarSinha and thanks for sharing this with the Community, glad that it’s working well so far!

Thanks to everyone for your help and valuable contributions! :orange_heart:

Regarding the Webhooks Journal API, a few points from the documentation and changelog may be especially helpful:

  1. Lightweight notification model: Instead of sending the full event payload, the Journal provides a lightweight notification, and you then query the Journal API for the complete event details. This changes the workflow from reconciling two independently pushed event streams to retrieving events from the Journal. It may help reduce, although not completely eliminate, ordering or timing differences between CREATE and UPDATE events.

  2. Granular subscriptions: With webhooks-journal/subscriptions/v4, you can subscribe using an explicit actions array that includes CREATE, UPDATE, DELETE, MERGE, RESTORE, ASSOCIATION_ADDED, ASSOCIATION_REMOVED, and SNAPSHOT. You can also narrow subscriptions to specific properties and objectIds.

    This doesn’t appear to prevent UPDATE events triggered during record creation. However, the Snapshots endpoint may offer a lighter way to compare state changes without running a full CRM Search query.

So, the Journal API doesn’t remove the need for correlation logic, but its snapshot and query model may allow you to replace the fallback contact-fetch step with a more efficient option.

I’d recommend reviewing the Webhooks Journal API reference for the latest details on the Snapshots endpoint.

Please let us know if you have additional questions!

Thanks everybody and enjoy the day! :sun:
Bérangère

This post was created with the assistance of AI tools