Skip to content

Salesforce Prep

Expert Tips for Interviews & Certifications

  • Home
  • Current Page Parent Start Learning
  • About Me
  • Privacy Policy
  • Home
  • Current Page Parent Start Learning
  • About Me
  • Privacy Policy

LIST OF CONTENT

    • Order of Execution, Transactions, Savepoints, Partial Success & Locking in Salesforce
    • Record Types, Page Layouts, Dynamic Forms, and Picklist Strategy
    • Relationship Design: Master-Detail vs. Lookup, Junctions, and Roll-Ups
    • Mastering Salesforce Data Types: Objects, Fields, External Objects, Big Objects & Polymorphic Fields
    • Multi-Tenancy, Governor (Trust) Limits, Hyperforce & Data Residency in Salesforce
    • See all Platform & Data Model posts
    • Exception Handling & Error Management in Apex
    • Advanced OOP Concepts in Apex
    • Object-Oriented Programming in Apex
    • Collections & Data Structures in Apex
    • Control Flow & Logic in Apex
    • Apex Language Fundamentals
    • Salesforce Architecture, Org Types & Metadata
    • Apex Syntax Basics
    • Secure Coding in Salesforce: SOQL Injection, XSS/CSRF, and Secrets Hygiene
    • Callouts in Apex: Secure Auth, Non-Blocking UX, and Resilient Retries
    • Error Strategy in Salesforce Apex: Custom Exceptions, Fail-Fast Validation, Correlation IDs & Telemetry
    • Apex Security Essentials — with/without sharing and CRUD/FLS with Security.stripInaccessible
    • Service / Repository / Unit of Work in Apex — Keeping the Domain Layer Clean
    • See all Basic Apex & Programming posts
    • Packaging & Managed Packages in Salesforce
    • Apex Design Patterns
    • Logging, Debugging & Monitoring in Apex
    • Caching & Performance Enhancements in Apex
    • Custom Metadata & Configuration
    • Dynamic Apex & Metadata Access
    • Apex with Flow & Automation
    • Event-Driven Architecture in Salesforce
    • REST Services & Serialization in Apex
    • Integrations & Callouts in Apex
    • Asynchronous Apex
    • Testing Advanced Scenarios in Apex
    • Testing Apex Code
    • Security & Sharing in Apex
    • Governor Limits & Performance Tuning
    • Trigger Frameworks & Best Practices
    • Triggers Fundamentals
    • External Event Consumption in Salesforce — Retries & Dead-Letter Concepts
    • Pub/Sub API vs Streaming API (CometD/EMP): Ordering & At-Least-Once Delivery in Salesforce
    • Platform Events vs Change Data Capture — Use Cases & Trade-offs (Salesforce)
    • Event Schema Versioning, Correlation IDs, Idempotency & Deduplication in Salesforce
    • See all Advanced Apex posts
    • DML Operations & Transactions in Apex
    • Advanced SOQL & SOSL
    • SOQL Fundamentals
    • Salesforce Flow – Overview & Key Concepts
    • Platform Events with Idempotent Consumers and Durable Replay Options (Salesforce)
    • Handling Large Data Volumes in Salesforce: Smart Batch Design, QueryMore, and Governor Limit Strategies
    • Chaining Strategies, State Management, Error Handling & Monitoring in Salesforce Apex
    • Future vs Queueable vs Batch vs Schedulable in Salesforce Apex: A Practical Selection Guide
    • Transactional Integrity in Apex: Mastering Partial Success, Savepoints, and Rollbacks
    • Trigger Timing, Cross-Object Updates, and Asynchronous Escalation in Salesforce Apex
    • Bulkification, Mixed DML, and Lock Avoidance in Salesforce Apex
    • One Trigger per Object, Handler Pattern, and Recursion Guards in Salesforce Apex
    • Platform Cache Patterns in Salesforce — Safe Caching & Invalidation
    • SOSL vs SOQL, Search Tuning, and Skinny Tables
    • Relationship & Aggregate SOQL, Subqueries, and Avoiding N+1 in Salesforce
    • Salesforce SOQL/SOSL Selectivity & Query Plan — Standard, Custom & Compound Indexes
    • See all Automation Process posts
    • Middleware Patterns (MuleSoft / Kafka / AWS / Azure) & Error Routing — A Practical Integration Guide
    • Salesforce Integration Simplified: Webhooks, Platform Events & Callouts
    • OAuth 2.0: Choosing the Right Flow for Your App
    • API Contracts You Can Trust: Versioning, Pagination, Error Model, Idempotency Keys
    • Salesforce API Guide: REST, SOAP, Bulk, Composite, and GraphQL Explained
    • See all APIs & Integrations posts
    • Packaging & Managed Packages in Salesforce
    • Deployment & DevOps in Salesforce
    • Advanced Apex & Edge Cases
    • Dependency Injection & Clean Architecture in Apex
    • See all Security & Delivery posts
    • No posts yet
    • See all LWC posts
    • No posts yet
    • See all VISUAL FORCE posts
    • Interview, Certification & Project Readiness
    • Real-World Apex Best Practices
    • See all Pro & Career posts
    • No posts yet
    • See all Sub Category posts

Home » Platform Events with Idempotent Consumers and Durable Replay Options (Salesforce)

Automation Process

Platform Events with Idempotent Consumers and Durable Replay Options (Salesforce)

Share

In Salesforce event-driven systems, Platform Events are delivered at least once — meaning the same message can show up multiple times or even arrive out of order. That’s completely fine, as long as your consumers are idempotent (safe to process more than once) and you have a durable replay strategy to recover cleanly from downtime without missing anything.

This guide walks through practical techniques to make your events safe, resilient, and replayable — including how to add deduplication keys, persist checkpoints, and subscribe with replay support.


1) Idempotency: Achieving “Exactly-Once” Results on an At-Least-Once Bus

Every reliable event consumer needs a way to detect duplicates and ensure business logic only runs once per unique event.

Deduplication key:
Each event should carry a unique and deterministic identifier — something like OrderId + Version, or a producer-generated UUID.

Idempotency store:
Before making any database updates or callouts, your consumer should attempt to insert this key into a unique indexed store.

  • If the insert succeeds → proceed with business logic.

  • If it fails (duplicate key) → safely skip the event.

Transactional boundary:
Commit the idempotency claim and the business side effects in the same transaction. That way, you never risk applying one without the other.

Prefer natural keys over hashes:
Meaningful keys that can be recomputed by the producer are easier to reason about and less prone to collision than payload hashes.


2) Durable Replay and Retention Options

Salesforce Platform Events keep messages for a fixed retention window (typically 72 hours), each identified by a ReplayId that increases with every new event. When a subscriber reconnects, it can choose a replay option:

  • LATEST: Start from new events only.

  • EARLIEST: Replay everything still within retention.

  • CUSTOM: Resume from a stored ReplayId checkpoint.

Client options:

  • CometD (Streaming API): Use the Replay Extension to set the ReplayId per channel.

  • Pub/Sub API (gRPC/REST): Set replayPreset or replayId directly. It’s faster, supports batching, and handles back-pressure better.

Checkpointing:
Always save the last successfully processed ReplayId after finishing business logic.
On restart, subscribe from that checkpoint to continue seamlessly. Even if duplicates are reprocessed, they’re handled safely by your idempotent consumer.


3) Ordering and Concurrency

Platform Events don’t guarantee global ordering, but you can design around it:

  • Partition by key (e.g., one stream per OrderId) to keep related events in order.

  • Serialize critical updates by locking on a single Idempotency__c row or a parent record if per-key ordering is essential.

  • Avoid global serialization — it destroys throughput. Only lock per business key when truly necessary.


4) Error Handling Best Practices

A robust consumer separates retryable and terminal errors:

  • Retry transient issues like timeouts or record locks.

  • Log and dead-letter validation or data errors for later review.

Add observability:
Track how many events you’ve processed, the last ReplayId, timestamps, and the most recent error.
This helps confirm forward progress and simplifies troubleshooting.


Real-World Example (with Code)

Scenario

Let’s say your fulfillment system publishes Order_Event__e messages for order creation, updates, and shipments.
On the receiving end, an Apex trigger consumes those events and updates the related Order__c records.
We’ll make this consumer idempotent with a custom store and also demonstrate a durable replay subscriber using the Pub/Sub API.


A) Platform Event Definition & Publisher (Producer)

// Order_Event__e fields (example):
// – OrderId__c (Text, required)
// – EventType__c (Text: ‘Created’ | ‘Updated’ | ‘Shipped’, required)
// – DedupKey__c (Text, required, unique upstream if possible)
// – Payload__c (Long Text Area or JSON)

// Order_Event__e fields (example):
// - OrderId__c (Text, required)
// - EventType__c (Text: 'Created'|'Updated'|'Shipped', required)
// - DedupKey__c (Text, required, unique upstream if possible)
// - Payload__c (Long Text Area or JSON)

// Example publish
public with sharing class OrderEventPublisher {
    public static void publishOrderEvent(String orderId, String type, String dedupKey, Object payload) {
        Order_Event__e evt = new Order_Event__e(
            OrderId__c   = orderId,
            EventType__c = type,
            DedupKey__c  = dedupKey,
            Payload__c   = JSON.serialize(payload)
        );
        Database.SaveResult sr = EventBus.publish(evt);
        System.assert(sr.isSuccess(), 'Failed to publish PE: ' + sr);
    }
}

B) Idempotency Store (Custom Object)

// Custom object: Idempotency__c
// Fields:
// - Key__c (Text, External ID, Unique)
// - OrderId__c (Lookup or Text)
// - ConsumedAt__c (Datetime)
// - ReplayId__c (Text)  // optional: for observability

C) Apex Platform Event Trigger (Idempotent Consumer)

// OrderEventTrigger.trigger (PE triggers are AFTER INSERT)
trigger OrderEventTrigger on Order_Event__e (after insert) {
    List<Idempotency__c> toClaim = new List<Idempotency__c>();
    Set<String> keys = new Set<String>();
    for (Order_Event__e e : Trigger.New) {
        if (String.isBlank(e.DedupKey__c)) continue;
        keys.add(e.DedupKey__c);
        toClaim.add(new Idempotency__c(
            Key__c = e.DedupKey__c,
            OrderId__c = e.OrderId__c,
            ConsumedAt__c = System.now()
            // ReplayId__c cannot be read in Apex; leave blank or populate via middleware
        ));
    }

    // Try to claim keys. Unique constraint prevents double-processing.
    Database.SaveResult[] claims = Database.insert(toClaim, /* allOrNone */ false);

    // Build a map from DedupKey to event for business processing
    Map<String, Order_Event__e> byKey = new Map<String, Order_Event__e>();
    for (Order_Event__e e : Trigger.New) if (e.DedupKey__c != null) byKey.put(e.DedupKey__c, e);

    List<Order__c> updates = new List<Order__c>();
    for (Integer i = 0; i < claims.size(); i++) {
        Database.SaveResult r = claims[i];
        Idempotency__c claimed = toClaim[i];
        if (!r.isSuccess()) {
            // Duplicate key → already processed. Skip safely.
            Boolean duplicate = false;
            for (Database.Error err : r.getErrors())
                if (err.getStatusCode() == StatusCode.DUPLICATE_VALUE) duplicate = true;
            if (duplicate) continue; else throw new DmlException('Idempotency claim failed: ' + r);
        }

        // First claim: safe to apply business side effects
        Order_Event__e evt = byKey.get(claimed.Key__c);
        if (evt == null) continue;

        if (evt.EventType__c == 'Created') {
            updates.add(new Order__c(
                External_Id__c = evt.OrderId__c,
                Status__c = 'Open'
            ));
        } else if (evt.EventType__c == 'Shipped') {
            updates.add(new Order__c(
                External_Id__c = evt.OrderId__c,
                Status__c = 'Shipped',
                Shipped_At__c = System.now()
            ));
        } else if (evt.EventType__c == 'Updated') {
            // parse JSON payload minimally
            Map<String, Object> p = (Map<String, Object>) JSON.deserializeUntyped(evt.Payload__c);
            updates.add(new Order__c(
                External_Id__c = evt.OrderId__c,
                Amount__c = (Decimal) p.get('amount')
            ));
        }
    }

    if (!updates.isEmpty()) {
        // Upsert by external id avoids duplicates and is bulk-safe
        Database.UpsertResult[] res = Database.upsert(updates, Order__c.Fields.External_Id__c, false);
        // Optional: collect and log row-level errors instead of throwing
    }
}

Why it’s idempotent:
The consumer inserts a unique DedupKey__c before doing anything else. If the same message replays, the insert fails with a DUPLICATE_VALUE, and the event is skipped safely — no side effects.


D) Durable Replay Subscriber (Pub/Sub API – Node.js Pseudo-Code)

This pattern is ideal for middleware or long-running worker services. It stores a replay checkpoint and resumes from the last processed ReplayId when restarted.

// Pseudo-code for Pub/Sub API (Node.js-like)
const channel = '/event/Order_Event__e';
let checkpoint = await repo.loadReplayId(channel); // e.g., from Postgres or Custom Metadata

const request = {
  topicName: channel,
  numRequested: 100,
  replayId: checkpoint || null,     // when null, use preset below
  replayPreset: checkpoint ? 'CUSTOM' : 'LATEST' // or 'EARLIEST'
};

const stream = await pubsub.subscribe(request);

for await (const msg of stream) {
  const replayId = msg.replayId; // Buffer/bytes
  const evt = JSON.parse(msg.payload.Payload__c);

  // Start a DB tx; write idempotency key first
  const claimed = await repo.tryClaim(evt.DedupKey__c);
  if (claimed) {
    await applyBusinessChanges(evt); // Upserts in your target system
    await repo.saveCheckpoint(channel, replayId); // commit checkpoint AFTER success
  } else {
    // Already processed → still advance the checkpoint
    await repo.saveCheckpoint(channel, replayId);
  }
}

Key Takeaways

  • Checkpoint after success: Only advance your ReplayId once business work completes.

  • CUSTOM replay allows subscribers to resume exactly where they left off.

  • Batch size and flow control help tune throughput while avoiding overload.

  • Platform Events

Final Thoughts

Design for at-least-once delivery by making every consumer idempotent.
Use a unique deduplication key and a claim-then-act approach to guarantee safe processing.
Persist replay checkpoints and subscribe with CUSTOM replay to recover cleanly after outages.
Serialize only where necessary and log enough metrics to prove progress.

When done right, Platform Events become a rock-solid backbone for Salesforce event-driven architectures — reliable, replayable, and safe at scale.

  • October 21, 2025

Tags: Access TokensAPI authenticationauthorization flowsdevice authorizationDurable SubscriptionsEvent-Driven ArchitectureIdempotent ConsumersJWT BearerOAuth 2.0PKCEReplayIdSalesforce Platform EventsSecure LoginToken Refreshweb security

Share on Facebook
Share on X
  • Next Event Schema Versioning, Correlation IDs, Idempotency & Deduplication in Salesforce
  • Previous Handling Large Data Volumes in Salesforce: Smart Batch Design, QueryMore, and Governor Limit Strategies

You may also like...

  • Governor Limits & Performance Tuning

    Governor Limits & Performance Tuning

  • Apex Design Patterns

    Apex Design Patterns

  • Event-Driven Architecture in Salesforce

    Event-Driven Architecture in Salesforce

  • salesforce apex future queueable batch schedulable feature image.jpg

    Future vs Queueable vs Batch vs Schedulable in Salesforce Apex: A Practical Selection Guide

  • salesforce apex bulkification mixed dml lock avoidance feature image.jpg

    Bulkification, Mixed DML, and Lock Avoidance in Salesforce Apex

  • Dependency Injection & Clean Architecture in Apex

    Dependency Injection & Clean Architecture in Apex

  • SOQL Fundamentals

    SOQL Fundamentals

  • A flat digital vector graphic features bold blue .png

    Apex Security Essentials — with/without sharing and CRUD/FLS with Security.stripInaccessible

  • Integrations & Callouts in Apex

    Integrations & Callouts in Apex

  • Advanced SOQL & SOSL

    Advanced SOQL & SOSL

  • salesforce apex trigger handler recursion guards feature image.jpg

    One Trigger per Object, Handler Pattern, and Recursion Guards in Salesforce Apex

  • Apex with Flow & Automation

    Apex with Flow & Automation

  • api contracts versioning pagination error model idempotency keys featured image.jpg

    API Contracts You Can Trust: Versioning, Pagination, Error Model, Idempotency Keys

  • Packaging & Managed Packages in Salesforce

    Packaging & Managed Packages in Salesforce

  • Apex Language Fundamentals

    Apex Language Fundamentals

Categories

Recent Posts

  • Interview, Certification & Project Readiness January 6, 2026
  • Real-World Apex Best Practices January 6, 2026
  • Packaging & Managed Packages in Salesforce January 6, 2026
  • Deployment & DevOps in Salesforce January 6, 2026
  • Advanced Apex & Edge Cases January 6, 2026
  • Dependency Injection & Clean Architecture in Apex January 6, 2026
  • Apex Design Patterns January 6, 2026
  • Logging, Debugging & Monitoring in Apex January 6, 2026
  • Caching & Performance Enhancements in Apex January 6, 2026
  • Custom Metadata & Configuration January 6, 2026

Tags

Selective Queries Selective SOQL Semi-Join Separation of Concerns Serverless Patterns Session cache sharing-and-security Shield Encryption Skinny Tables SNS Fanout Soft TTL SOQL soql-injection SOQL best practices SOQL Injection Prevention SOQL vs SOSL SOSL SQS DLQ Standard Objects Step Functions Streaming API Streaming API CometD stripinaccessible Testability Token Refresh Transactional Integrity Transaction Management Trigger Handler Pattern Triggers Trigger Timing Trust Limits Type-ahead search TYPEOF UI API Unit of Work UX Versioned keys versioning web security WhatId WhoId Without Sharing with sharing WITH SNIPPETS xss-csrf

RECENT POSTS

  • Interview, Certification & Project Readiness
  • Real-World Apex Best Practices
  • Packaging & Managed Packages in Salesforce
  • Deployment & DevOps in Salesforce
  • Advanced Apex & Edge Cases

SEARCH

Salesforce Prep © 2026. All Rights Reserved.