- Added support for Salesforce Platform Events, specifically subscribing to `OrderProvisionRequested__e` to trigger provisioning jobs. - Introduced new environment variables for Salesforce event configuration, including SF_EVENTS_ENABLED, SF_PROVISION_EVENT_CHANNEL, and SF_PUBSUB_ENDPOINT. - Refactored order fulfillment process to utilize event-driven architecture, enhancing reliability and scalability. - Updated documentation to reflect changes in the provisioning workflow and environment variable requirements. - Removed deprecated webhook handling code to streamline the integration.
5.9 KiB
Salesforce-to-Portal Order Communication Guide
Note: 2025 update — Async-first via Salesforce Platform Events is now the recommended pattern. The legacy webhook path remains referenced only historically and should be phased out.
Overview
This guide focuses specifically on secure communication between Salesforce and your Portal for order provisioning. This is NOT about invoices or billing - it's about the order approval and provisioning workflow.
The Order Flow
1. Customer places order → Portal creates Salesforce Order (Status: "Pending Review")
2. Salesforce operator approves Order
3. Salesforce Flow publishes OrderProvisionRequested__e
4. Portal subscriber enqueues a job → provisions in WHMCS → updates Salesforce Order status
5. Customer sees updated status in Portal
1. Salesforce → Portal (Order Provisioning)
Recommended (2025): Async via Platform Events ✅
High-level flow
1. Operator marks Order Approved in Salesforce
2. Record-Triggered Flow publishes Order_Fulfilment_Requested__e
3. Portal subscribes via Pub/Sub API (gRPC) and enqueues a provisioning job
4. Portal provisions in WHMCS and updates Salesforce Order status
5. Portal UI polls BFF for status (unchanged)
Salesforce Setup
- Create High-Volume Platform Event:
Order_Fulfilment_Requested__e- Fields (example API names):
OrderId__c(Text 18) — requiredIdemKey__c(Text 80) — optional (Idempotency key)CorrelationId__c(Text 80) — optionalRequestedBy__c(Text 80) — optionalVersion__c(Number) — optional
- Fields (example API names):
- Flow: Record-Triggered on Order when Status changes to Approved
- Actions:
- Update
Activation_Status__c = Activating - Create
OrderProvisionRequested__eand setOrderId__c = $Record.Id - Optionally clear activation error fields
- Update
- Actions:
Portal Setup
- Env
SF_EVENTS_ENABLED=trueSF_PROVISION_EVENT_CHANNEL=/event/OrderProvisionRequested__eSF_EVENTS_REPLAY=LATEST(orALLto start from earliest retained)
- Subscriber (auto-start, durable replay):
apps/bff/src/vendors/salesforce/events/pubsub.subscriber.ts- Subscribes to the channel and enqueues a job to the
provisioningqueue
- Subscribes to the channel and enqueues a job to the
- Worker:
apps/bff/src/orders/queue/provisioning.processor.ts- Guards on
Activation_Status__c = Activating; provisions in WHMCS and then updates Salesforce
- Guards on
Why async: resilient (replay within retention), decoupled, no inbound allowlisting. No auto-retries from the portal; operators retry from Salesforce by flipping status to Activating.
Legacy Implementation (Webhook) — Removed
The old endpoint POST /orders/{sfOrderId}/fulfill has been removed. Provisioning is now triggered solely via Platform Events published from Salesforce.
Enhanced Security Implementation (Legacy Webhook)
All Quick Actions and Named Credentials previously used to call the portal should be retired.
Salesforce Apex Implementation (not needed)
No Apex callout to the portal is required. Use a Record-Triggered Flow to publish the Platform Event.
2. Security Configuration
Environment Variables
# Platform Events (BFF)
SF_EVENTS_ENABLED=true
SF_PROVISION_EVENT_CHANNEL=/event/OrderProvisionRequested__e
SF_EVENTS_REPLAY=LATEST # or ALL
Salesforce Named Credential
Not required for the async path; the portal pulls events from Salesforce.
4. Customer Experience
Portal UI Polling
The portal should poll for order status updates:
// In your Portal UI
export function useOrderStatus(sfOrderId: string) {
const [status, setStatus] = useState<OrderStatus>('Pending Review');
useEffect(() => {
const pollStatus = async () => {
try {
const response = await fetch(`/api/orders/${sfOrderId}`);
const data = await response.json();
setStatus(data.status);
// Stop polling when order is complete
if (['Provisioned', 'Failed'].includes(data.status)) {
clearInterval(interval);
}
} catch (error) {
console.error('Failed to fetch order status:', error);
}
};
const interval = setInterval(pollStatus, 5000); // Poll every 5 seconds
pollStatus(); // Initial fetch
return () => clearInterval(interval);
}, [sfOrderId]);
return status;
}
5. Monitoring and Alerting
Key Metrics to Monitor
- Provisioning Success Rate: Track successful vs failed provisioning attempts
- Provisioning Latency: Time from SF approval to completion
- WHMCS API Errors: Monitor WHMCS integration health
- Event Lag: Time between event publish and job enqueue/complete
Alert Conditions
// Example monitoring service
@Injectable()
export class OrderProvisioningMonitoringService {
async recordProvisioningAttempt(sfOrderId: string, success: boolean, duration: number) {
// Record metrics
this.metricsService.increment('order.provisioning.attempts', {
success: success.toString(),
});
this.metricsService.histogram('order.provisioning.duration', duration);
// Alert on high failure rate
const recentFailureRate = await this.getRecentFailureRate();
if (recentFailureRate > 0.1) { // 10% failure rate
await this.alertingService.sendAlert('High order provisioning failure rate');
}
}
}
6. Testing
Event Flow Testing
Validate that an OrderProvisionRequested__e event enqueues a job and runs the orchestrator. Check logs and /health/sf-events for status and cursor.
Summary
This focused approach ensures secure communication specifically for your order provisioning workflow:
- Salesforce Flow → Publishes Platform Event
OrderProvisionRequested__e - Portal BFF → Subscribes, queues job, provisions in WHMCS, updates Salesforce
- Customer → Sees real-time order status in Portal UI
The security relies on your existing JWT-based Salesforce API integration and ; no inbound webhooks are required.