The partial-batch path is where I’d be most careful. Returning 500 because two items failed causes HubSpot to retry the entire batch, so the other 98 successful events come back too. I’d persist every raw event independently first, acknowledge the batch once that durable write succeeds, and let an internal retry queue handle only the failed event IDs. Pair the event ID with object ID and subscription type, then make the downstream update conditional on occurredAt or record version so an older retry can’t overwrite newer CRM state. I’m Ege, co-founder of Atlantic, based in SF—we’ve run into this exact “successful delivery, wrong business state” failure mode. Delivery durability matters, but per-event state is what keeps the CRM trustworthy.
The partial-batch path is where I’d be most careful. Returning 500 because two items failed causes HubSpot to retry the entire batch, so the other 98 successful events come back too. I’d persist every raw event independently first, acknowledge the batch once that durable write succeeds, and let an internal retry queue handle only the failed event IDs. Pair the event ID with object ID and subscription type, then make the downstream update conditional on occurredAt or record version so an older retry can’t overwrite newer CRM state. I’m Ege, co-founder of Atlantic, based in SF—we’ve run into this exact “successful delivery, wrong business state” failure mode. Delivery durability matters, but per-event state is what keeps the CRM trustworthy.