Your webhook testing failures are due to incorrect assumptions about batch email processing and webhook delivery guarantees. Here’s a comprehensive solution addressing webhook testing, async validation, batch operations, and event-driven testing:
Webhook Testing Architecture:
Implement a webhook collection service separate from your test runner:
// Webhook collector with buffering
class WebhookCollector {
constructor(batchId) {
this.batchId = batchId;
this.events = new Map();
this.expectedCount = 0;
}
async collect(timeout = 300000) {
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
if (this.events.size >= this.expectedCount * 0.95) break;
await sleep(5000);
}
return Array.from(this.events.values());
}
}
Batch Operations Strategy:
Add correlation metadata to batch sends:
const batchId = uuidv4();
const emails = recipientList.map(recipient => ({
to: recipient.email,
customProperties: {
test_batch_id: batchId,
test_sequence: recipient.index
}
}));
await hubspot.marketing.batch.send(campaignId, emails);
Webhook handler extracts batch_id for correlation:
app.post('/webhook/email-event', (req, res) => {
const { eventType, emailId, properties } = req.body;
const batchId = properties.test_batch_id;
webhookCollector.recordEvent(batchId, emailId, eventType);
res.status(200).send();
});
Async Validation Pattern:
Use progressive validation thresholds:
- Quick check (30s): ≥60% events received (catches major failures)
- Normal check (2m): ≥90% events received (standard validation)
- Extended check (5m): ≥95% events received (comprehensive validation)
Event-Driven Testing Best Practices:
- Never expect 100% webhook delivery (network issues, HubSpot rate limits)
- Use idempotent webhook handlers (HubSpot may retry)
- Implement webhook signature verification (prevent test pollution)
- Store webhook events with timestamps for debugging
- Add circuit breaker for webhook endpoint failures
Handling Missing Webhooks:
For the 5-10% missing events, implement fallback validation:
const missingEvents = await findMissingWebhooks(batchId);
for (const emailId of missingEvents) {
const status = await hubspot.marketing.email.getStatus(emailId);
webhookCollector.recordFallback(batchId, emailId, status);
}
Realistic Test Expectations:
For 500-email batches:
- Webhook arrival: 2-8 minutes (HubSpot processes ~100-150/minute)
- Delivery rate: 93-98% webhook receipt is excellent
- Event ordering: Not guaranteed, use timestamps
- Timeout settings: Minimum 5 minutes for batch validation
Your current 30-second timeout is far too aggressive for batch operations. With the collector pattern and progressive validation, your tests will achieve 95%+ reliability while accurately reflecting real-world webhook behavior. The key insight: test for eventual consistency, not immediate completeness.
This draft is based on general HubSpot knowledge. It has not been verified against your specific version and environment. Practitioners: verify the steps and share your experience below.