Stop Letting Webhooks Hit Your Triggers: Platform Events as the Shock Absorber for High-Volume Integrations

The Platform Events webhook buffer pattern from three production Salesforce builds: CTI dispositions, a 38-field EHR event, and 100,000+ patient messages.

Reviewed July 19, 2026 · 9 min read · Playbook · By GAT Solutions

Every high-volume Salesforce integration we get called to rescue has the same autopsy. A vendor — a dialer, an EHR, a form tool, an e-commerce platform — fires webhooks at an Apex REST endpoint, and the endpoint does the obvious thing: it writes records. It worked in the demo. Then a Monday morning happened.

This article is the pattern we now apply by default, drawn from three builds running in production today: call dispositions at a high-velocity legal intake operation, a bidirectional EHR sync at a psychiatric practice, and patient chat plus access auditing in a surgical-care org. Same architecture, three very different payloads. The numbers come from our own code audits and org queries, not from a vendor slide.

The failure mode: bursts meet synchronous DML

Webhooks do not arrive politely spaced. A call center closes out a campaign and the dialer flushes dispositions in a burst. A clinic opens and the EHR replays the morning's schedule import. A form vendor retries everything it queued during its own outage. The traffic shape is spiky by nature — and a synchronous handler pays for every spike three times.

First, the handler's DML runs inside the webhook's execution context, so every insert counts against governor limits while the vendor's HTTP client is holding a connection open. Second, those writes fire whatever triggers and record-triggered flows already live on the target objects — automation the integration author has usually never read — so an innocent status update cascades, and two near-simultaneous webhooks for the same record collide in a row lock. Third, and worst: the vendor frequently receives its 200 before the failure happens. Nothing retries. The two systems drift apart one dropped webhook at a time, and nobody notices until a report disagrees with reality.

The synchronous path should contain nothing that can fail slowly. Validate, publish, return. Everything else belongs on the other side of the event bus.

The pattern: the handler does one job

The fix is an old integration principle — separate receiving an event from acting on it — built entirely from Salesforce-native parts:

  • Edge: an Apex REST handler validates the payload, maps it onto a Platform Event, publishes, and returns. No DML, no queries against business objects, no callouts.
  • Buffer: the Platform Event bus absorbs the burst. Publishing is cheap and fast; the handler returns in milliseconds, so the vendor never times out and never retries unnecessarily.
  • Workers: subscriber Flows or event triggers do the writes asynchronously, idempotently, in fresh execution contexts with their own governor limits — at a pace the platform schedules, with replay if a subscriber falls behind.
  • Observability: a dedicated error-log object catches every subscriber failure, because the second-worst outcome is failure and the worst is silent failure.

That is the whole pattern. What makes it worth an article is how far it stretches — the same four boxes carried telephony, an EHR, and a real-time chat system without changing shape.

Case 1 — Telephony: buffering a dialer's dispositions

Tort Experts runs a high-velocity legal intake operation where we own the sales and marketing operations systems — the go-to-market machinery around a contact center that lives on the phones. Their Five9 dialer reports every call outcome by webhook: disposition, agent, duration, campaign, timestamps.

Two problems. Volume: dispositions arrive at conversation speed across a full intake floor, in bursts whenever a campaign list flushes. And format: Five9's payload is not JSON. It is a key=value pseudo-JSON string that no standard parser accepts, with its own quoting quirks. The temptation is to parse it, match the contact, create the task, and update the campaign — all in the handler. That version falls over on the first busy morning.

The shipped version is a REST handler of roughly 120 lines whose entire job is a tolerant parser and a publish call. It normalizes the pseudo-JSON into typed fields on a dedicated event — Five9_Task_Event__e — and returns. A subscriber trigger on the event does the matching and the task DML asynchronously, with its own test coverage. The dialer never waits on Salesforce automation, and a malformed payload lands in a log instead of a governor-limit stack trace. Call outcomes flow into the same routing and reporting spine we describe in our lead-routing architecture guide.

Case 2 — EHR: a 38-field patient event

For a multi-state psychiatric health practice, the inbound traffic is AdvancedMD webhooks — patient demographics and appointment changes — and the stakes are higher: a duplicated or half-updated patient record is not a cosmetic bug in behavioral health.

Same pattern, different payload width. The REST handlers (one for demographics, one for appointments, each deliberately small, each with its own test class) validate and publish Patient_Event__e, a deliberately fat 38-field event carrying everything a subscriber needs to upsert a Person Account without calling back into the EHR. A skinny "record 4127 changed, go fetch" event would reintroduce callout limits and a second failure mode exactly where we removed the first one. Subscriber Flows match on the EHR's patient identifier and upsert idempotently, because webhooks redeliver.

The full build — including the outbound push and MRN writeback that make it bidirectional — is written up in our EHR–Salesforce Platform Events architecture. For this article the point is narrower: the buffer pattern didn't change when the payload went from a call disposition to protected health information. Only the validation rules and the field count did.

Case 3 — Chat and audit: the pattern at sustained volume

The strongest evidence that the buffer holds is what happens when the "webhook" is your own users, all day, every day. At Goldfinch Health, a surgical-care navigation program, we built real-time patient messaging directly on the event bus: a message insert publishes a chat Platform Event, and Lightning Web Components subscribe on both the nurse and patient side. That system has managed 100,000+ patient messages to date across 51 active users, alongside 15,314 surgeries managed in the same org.

The second event in that org does compliance work: every time a user views patient PHI, an access event is published and persisted asynchronously for audit reporting — 34,860 access-audit events logged so far, without adding a synchronous write to every record view. Two authored Platform Events, two entirely different jobs, one architecture. We've detailed both in the real-time chat build and the access-audit pipeline.

BuildInbound sourceEventProof in production
Legal intake (Tort Experts)Five9 dialer webhooks (key=value pseudo-JSON)Five9_Task_Event__e~120-line handler + async subscriber, audited across two org snapshots
Psychiatric practice (anonymized)AdvancedMD EHR webhooks38-field Patient_Event__eBidirectional sync in production; MRN writeback closes the identity loop
Surgical care (Goldfinch Health)In-org chat + PHI access activity2 authored Platform Events100,000+ messages managed; 34,860 access-audit events

Design details that make or break it

Idempotency is non-negotiable. The bus can deliver an event more than once, and vendors redeliver webhooks. Every subscriber write must key on an external identifier and be safe to run twice. If you cannot describe the idempotency key, the design is not finished.

Fund the error path like a feature. Subscribers fail out-of-band, where no vendor is listening for an error response. A dedicated error-log object — payload metadata, status, timestamp, never sensitive narrative — plus an alert and a human who owns the queue is the difference between a blip and a quiet month of drift.

Respect replay, and test for it. The event bus retains events for a replay window, and a resubscribing trigger resumes from its last replay ID. That is your safety net during subscriber deployments — but only if subscribers tolerate a batch of old events arriving at once.

Mind the ceilings. Events have payload size limits, orgs have daily publish allocations, and ordering is only guaranteed within limits you should read before you commit. Size the event to the write it feeds — fat enough to avoid callbacks, no wider.

Know when to say no. If the caller needs a synchronous answer, if volume exceeds your allocation, or if many external systems need the same stream with heavy transformation, this is not your pattern — that is a request/reply design or a middleware conversation. Platform Events are a shock absorber, not a message broker for your whole enterprise.

Reference implementation, sanitized

The edge handler, reduced to its skeleton. The names are generic; the shape is exactly what runs in the builds above.

@RestResource(urlMapping='/vendor/events/*')
global with sharing class VendorWebhookHandler {
    @HttpPost
    global static void handle() {
        RestResponse res = RestContext.response;
        try {
            // 1. Parse + validate. Tolerant parsing lives HERE,
            //    not in subscribers.
            Map<String, String> payload =
                VendorPayloadParser.parse(
                    RestContext.request.requestBody.toString());
            if (!VendorPayloadParser.isValid(payload)) {
                LogService.logRejected(payload); // visible, not silent
                res.statusCode = 400;
                return;
            }
            // 2. Publish. The ONLY state change in this context.
            EventBus.publish(new Vendor_Event__e(
                External_Id__c = payload.get('id'),
                Type__c        = payload.get('type'),
                Payload_A__c   = payload.get('fieldA')
                // ...typed fields, sized to the subscriber's write
            ));
            res.statusCode = 200; // fast ack; work happens async
        } catch (Exception e) {
            LogService.logError('VendorWebhookHandler', e);
            res.statusCode = 500; // let the vendor retry
        }
    }
}

The subscriber — an event trigger or a platform-event-triggered Flow — carries the idempotent upsert, the business branching, and its own error logging. Notice what the handler lacks: no SOQL against business objects, no DML, no callouts, nothing that can hold a lock or trip a limit while the vendor waits.

Where this fits in your org

This pattern is the front door; what happens behind it is the automation layer that actually moves your business — routing, follow-up, document generation, census math. That layering is the core of our Salesforce automation practice, and the vendor-facing half — contracts, parsing, retry semantics, auth — is integration engineering. If your inbound traffic carries PHI, the healthcare-specific posture is on our Salesforce for healthcare page — HIPAA-conscious by design, with the caveat that compliance is a property of your program, not of any integration architecture.

Frequently asked questions

What is the Platform Events webhook buffer pattern?

An inbound Apex REST handler does exactly two things: validate the payload and publish a Platform Event. It performs no DML and no queries against business objects, so it returns in milliseconds and never fires a trigger cascade. Asynchronous subscribers — Flows or event triggers — do the actual writes on the other side of the event bus, at a pace the platform manages, with replay if a subscriber falls behind.

Why not just write records directly from the webhook handler?

Because webhooks arrive in bursts, and a synchronous handler doing DML inherits every trigger, record-triggered flow, and governor limit on the objects it touches. Two near-simultaneous webhooks for the same record collide in a row lock, and the vendor often gets its 200 response before the failure happens — so nothing retries and the systems silently drift. Publishing an event is one of the cheapest operations on the platform; that is what belongs in the synchronous path.

Do Platform Events guarantee delivery?

Publish is not transactional with your response, and subscribers can fail independently — so no, not by themselves. The pattern gets its reliability from three additions: idempotent subscribers keyed on an external ID so redelivery is safe, a dedicated error-log object so every subscriber failure is visible to a human, and the event bus replay window so a paused subscriber resumes where it stopped instead of losing messages.

When should you NOT use Platform Events for an integration?

When the caller needs a synchronous answer (a quote, a validation result), when you need guaranteed ordering across high concurrency beyond what a single partition gives you, when payloads exceed event size limits, or when message volume exceeds your org's daily publish allocation. And if many external systems need the same events with heavy transformation, dedicated middleware starts earning its cost. For bursty inbound webhooks landing in one org, events are usually the right buffer.

How do you handle a webhook payload that isn't clean JSON?

Parse it at the edge and normalize before publishing. One telephony vendor we integrated sends dispositions as key=value pseudo-JSON that no standard parser accepts, so the REST handler owns a small tolerant parser, maps the result onto typed event fields, and rejects anything that fails validation into a visible log. Subscribers only ever see the clean, typed Platform Event — the mess stays quarantined at the boundary.

Bring us the integration that falls over on Mondays

GAT Solutions builds revenue-generating systems for healthcare, accounting, and SaaS companies — fast, flawlessly, and powered by AI.

If a vendor webhook is writing straight into your triggers — or already failing quietly — bring the payload and the debug logs to a working session. We'll map the event architecture and show you the buffer before you spend a dollar.

Continue the decision

View all resources →

Initial systems consultation

Want this working in your org, not just in a blog post?

  • You leave with
  • A focused consultation with a senior systems consultant
  • A current-state fit and architecture read
  • A recommended path — implementation, ongoing ownership, or product