This guide provides comprehensive integration patterns for building robust Orderful integrations, including architecture decisions, communication patterns, and implementation strategies
Table of Contents
- Communication Patterns
- Architecture Patterns
- Transaction Lifecycle Management
- Error Handling & Resilience
- Security & Authentication
- Performance & Scaling
Communication Patterns
Pattern 1: Webhook Push (Event-Driven)
Best for: Real-time processing, high-volume integrations, modern microservices architectures
┌─────────────────┐ ┌─────────────┐ ┌──────────────────┐
│ Trading Partner │────────▶│ Orderful │────────▶│ Your System │
│ │ EDI │ Platform │ HTTP │ (Event Handler) │
└─────────────────┘ └─────────────┘ POST └──────────────────┘
│ │
│ Transaction Created │
│────────────────────────▶│
│ │
│◀────── HTTP 202 ────────│
│ (Async Processing) │
│ │
│◀───── Approve API ──────│
│ (After Processing) │
Implementation: Inbound HTTP (Webhook)
Setup:
- Navigate to Settings → Communication Channels
- Click Create New and select HTTP
- Enter your endpoint URL (e.g.,
https://api.yourcompany.com/orderful/webhook) - Configure authentication (OAuth 2.0 or Basic Auth)
Webhook Payload:
{
"id": 12345,
"transactionType": "850",
"direction": "inbound",
"stream": "live",
"sender": {
"isaId": "RETAILCORP",
"name": "Retail Corporation"
},
"receiver": {
"isaId": "YOURCOMPANY",
"name": "Your Company"
},
"delivery": {
"id": 67890,
"status": "pending",
"links": {
"approve": "https://api.orderful.com/v3/deliveries/67890/approve",
"fail": "https://api.orderful.com/v3/deliveries/67890/fail"
}
},
"createdAt": "2026-01-29T10:30:00Z"
}Response Handling:
Synchronous Processing (HTTP 200):
// Your endpoint handler
app.post('/orderful/webhook', async (req, res) => {
try {
const transaction = req.body;
// Process transaction immediately
await processTransaction(transaction);
// Respond with 200 - Orderful auto-approves delivery
res.status(200).json({ received: true });
} catch (error) {
// Respond with error - delivery marked as failed
res.status(500).json({ error: error.message });
}
});Asynchronous Processing (HTTP 202 - Recommended):
app.post('/orderful/webhook', async (req, res) => {
const transaction = req.body;
// Immediately acknowledge receipt
res.status(202).json({
received: true,
message: "Processing asynchronously"
});
// Process in background
processTransactionAsync(transaction)
.then(() => {
// Explicitly approve after successful processing
return approveDelivery(transaction.delivery.id);
})
.catch((error) => {
// Explicitly fail with reason
return failDelivery(transaction.delivery.id, error.message);
});
});
async function approveDelivery(deliveryId) {
return axios.post(
`https://api.orderful.com/v3/deliveries/${deliveryId}/approve`,
{},
{
headers: {
'orderful-api-key': process.env.ORDERFUL_API_KEY
}
}
);
}
async function failDelivery(deliveryId, reason) {
return axios.post(
`https://api.orderful.com/v3/deliveries/${deliveryId}/fail`,
{ reason },
{
headers: {
'orderful-api-key': process.env.ORDERFUL_API_KEY
}
}
);
}Advantages:
- ✅ Real-time notifications
- ✅ No polling overhead
- ✅ Lower latency
- ✅ Scales automatically with traffic
Considerations:
- ⚠️ Requires publicly accessible endpoint
- ⚠️ Must handle webhook retries
- ⚠️ Need webhook signature verification
Pattern 2: API Polling (Pull-Based)
Best for: Simpler deployments, batch processing, legacy systems, firewall restrictions
┌──────────────────┐ ┌─────────────┐
│ Your System │────────▶│ Orderful │
│ (Poller) │ GET │ Inbox │
└──────────────────┘ /inbox └─────────────┘
│ │
│ Poll every N seconds │
│─────────────────────────▶│
│ │
│◀───── Transactions ──────│
│ │
│ Process & Approve │
│─────────────────────────▶│
Implementation: Inbound Poller API
Basic Polling Loop:
const POLL_INTERVAL_MS = 30000; // 30 seconds
async function pollInbox() {
try {
const response = await axios.get(
'https://api.orderful.com/v4/inbox',
{
headers: {
'orderful-api-key': process.env.ORDERFUL_API_KEY,
'orderful-api-version': 'v4'
}
}
);
const transactions = response.data.items;
for (const transaction of transactions) {
await processTransaction(transaction);
// Approve delivery after successful processing
await approveDelivery(transaction.delivery.id);
}
} catch (error) {
console.error('Polling error:', error);
}
// Schedule next poll
setTimeout(pollInbox, POLL_INTERVAL_MS);
}
// Start polling
pollInbox();Advanced Polling with Concurrency Control:
const pLimit = require('p-limit');
const limit = pLimit(5); // Process max 5 transactions concurrently
async function pollInboxAdvanced() {
try {
const response = await axios.get(
'https://api.orderful.com/v4/inbox',
{
headers: {
'orderful-api-key': process.env.ORDERFUL_API_KEY,
'orderful-api-version': 'v4'
},
params: {
limit: 50,
transactionType: '850,860', // Filter specific types
stream: 'live'
}
}
);
const transactions = response.data.items;
// Process transactions concurrently with limit
await Promise.all(
transactions.map(transaction =>
limit(async () => {
try {
await processTransaction(transaction);
await approveDelivery(transaction.delivery.id);
} catch (error) {
await failDelivery(
transaction.delivery.id,
`Processing failed: ${error.message}`
);
}
})
)
);
} catch (error) {
console.error('Polling error:', error);
}
setTimeout(pollInboxAdvanced, POLL_INTERVAL_MS);
}Advantages:
- ✅ Simpler infrastructure (no public endpoint)
- ✅ Works behind firewalls
- ✅ Full control over processing timing
- ✅ Batch processing friendly
Considerations:
- ⚠️ Higher latency (depends on poll interval)
- ⚠️ Continuous polling overhead
- ⚠️ Need to manage poll state
Pattern 3: Hybrid (Webhook + Polling Fallback)
Best for: Mission-critical integrations requiring maximum reliability
// Webhook handler
app.post('/orderful/webhook', async (req, res) => {
const transaction = req.body;
// Mark as received via webhook
await redis.set(`tx:${transaction.id}:webhook`, 'received', 'EX', 3600);
res.status(202).json({ received: true });
await processTransactionAsync(transaction);
});
// Backup poller (runs every 5 minutes)
async function backupPoller() {
const transactions = await getInboxTransactions();
for (const tx of transactions) {
const webhookReceived = await redis.get(`tx:${tx.id}:webhook`);
if (!webhookReceived) {
console.log(`Webhook missed transaction ${tx.id}, processing via poller`);
await processTransactionAsync(tx);
}
}
}
setInterval(backupPoller, 300000); // 5 minutesArchitecture Patterns
Pattern 1: Direct Integration
Best for: Small-scale integrations, single applications, MVPs
┌─────────────────┐ ┌──────────────────┐
│ Orderful API │◀───────▶│ Your App │
│ │ │ (Monolith) │
└─────────────────┘ └──────────────────┘
│
▼
┌──────────────────┐
│ Database │
└──────────────────┘
Implementation:
// All logic in single application
class OrderfulService {
async handleInboundPurchaseOrder(transaction) {
// 1. Validate transaction
const validation = this.validatePO(transaction);
if (!validation.valid) {
throw new Error(validation.errors);
}
// 2. Store in database
await db.purchaseOrders.insert({
orderfulTransactionId: transaction.id,
poNumber: transaction.message.purchaseOrder.purchaseOrderNumber,
customerId: transaction.sender.isaId,
items: transaction.message.purchaseOrder.lineItems,
status: 'received'
});
// 3. Send acknowledgment
await this.sendPOA(transaction);
return { success: true };
}
}Pattern 2: Event-Driven Microservices
Best for: Large-scale systems, complex workflows, multiple teams
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Orderful API │────────▶│ Integration │────────▶│ Message Queue │
│ │ │ Gateway │ │ (RabbitMQ/SQS) │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│
┌─────────────────────────────────────────────┼─────────┐
▼ ▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌────────────────┐ ┌─────────┐
│ PO Handler │ │ ASN Handler │ │ Invoice │ │ ... │
│ Service │ │ Service │ │ Handler │ │ │
└───────────────┘ └───────────────┘ └────────────────┘ └─────────┘
│ │ │
▼ ▼ ▼
┌───────────────────────────────────────────────────────────┐
│ Shared Database or Event Store │
└───────────────────────────────────────────────────────────┘
Implementation:
Gateway Service:
// integration-gateway/webhook-handler.js
app.post('/webhook', async (req, res) => {
const transaction = req.body;
// Immediately acknowledge
res.status(202).json({ received: true });
// Route to appropriate queue based on transaction type
const routingKey = `transaction.${transaction.transactionType}.inbound`;
await messageQueue.publish('orderful-transactions', routingKey, {
transactionId: transaction.id,
type: transaction.transactionType,
deliveryId: transaction.delivery.id,
payload: transaction
});
});Purchase Order Handler Service:
// po-handler-service/index.js
messageQueue.subscribe('orderful-transactions', 'transaction.850.*', async (msg) => {
try {
const { transactionId, deliveryId, payload } = msg;
// Get full transaction content
const content = await orderfulClient.getTransactionContent(transactionId);
// Process PO
await processPurchaseOrder(content);
// Approve delivery
await orderfulClient.approveDelivery(deliveryId);
// Send acknowledgment
await sendPOA(content);
msg.ack();
} catch (error) {
console.error('PO processing error:', error);
// Fail delivery with reason
await orderfulClient.failDelivery(msg.deliveryId, error.message);
// Requeue or send to DLQ
msg.nack();
}
});Advantages:
- ✅ Decoupled services
- ✅ Independent scaling
- ✅ Fault isolation
- ✅ Team autonomy
Pattern 3: Adapter/Translation Layer
Best for: Integrating with existing ERP/WMS systems, legacy applications
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Orderful API │◀───────▶│ EDI Adapter │◀───────▶│ Legacy ERP │
│ (Modern JSON) │ │ (Translation) │ │ (Proprietary) │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│
▼
┌──────────────────┐
│ Mapping Rules │
│ Database │
└──────────────────┘
Implementation:
class EDIAdapter {
// Translate Orderful Mosaic to ERP format
async translateInbound(orderfulTransaction) {
const po = orderfulTransaction.message.purchaseOrder;
// Apply mapping rules
const erpFormat = {
order_header: {
external_order_id: po.purchaseOrderNumber,
customer_code: await this.mapCustomerCode(
orderfulTransaction.sender.isaId
),
order_date: this.formatDate(po.purchaseOrderDate),
requested_ship_date: this.formatDate(po.requestedDeliveryDate)
},
order_lines: po.lineItems.map(item => ({
line_num: item.lineNumber,
sku: this.mapSKU(item.buyerPartNumber, item.vendorPartNumber),
qty: item.quantity,
uom: this.mapUOM(item.unitOfMeasure),
price: item.unitPrice
}))
};
// Send to ERP via its API/flat file/database
return await this.erpClient.createOrder(erpFormat);
}
// Translate ERP response to Orderful format
async translateOutbound(erpShipment) {
return {
transactionType: "856",
message: {
shipNotice: {
shipmentId: erpShipment.shipment_id,
shipDate: this.parseDate(erpShipment.ship_date),
estimatedDeliveryDate: this.parseDate(erpShipment.est_delivery),
carrier: {
name: erpShipment.carrier_name,
scac: this.lookupSCAC(erpShipment.carrier_name),
trackingNumber: erpShipment.tracking_num
},
packages: erpShipment.packages.map(pkg => ({
packageId: pkg.package_id,
weight: pkg.weight_lbs,
weightUnit: "LB",
lineItems: pkg.items.map(item => ({
lineNumber: item.line_num,
quantity: item.qty_shipped,
purchaseOrderNumber: erpShipment.po_number
}))
}))
}
}
};
}
}Transaction Lifecycle Management
Comprehensive Transaction State Machine
┌─────────────┐
│ RECEIVED │ ← Transaction arrives from trading partner
└──────┬──────┘
│
▼
┌─────────────┐
│ VALIDATED │ ← Schema validation passed
└──────┬──────┘
│
├──▶ ┌─────────────┐
│ │ DELIVERED │ ← Approved via delivery API
│ └─────────────┘
│
└──▶ ┌─────────────┐
│ FAILED │ ← Rejected via delivery API
└─────────────┘
┌─────────────┐
│ OUTBOUND │ ← You create transaction
└──────┬──────┘
│
▼
┌─────────────┐
│ SENT │ ← Transmitted to trading partner
└──────┬──────┘
│
├──▶ ┌─────────────┐
│ │ ACKNOWLEDGED│ ← Partner confirms receipt
│ └─────────────┘
│
└──▶ ┌─────────────┐
│ REJECTED │ ← Partner rejects
└─────────────┘
Tracking Related Transactions
class TransactionLifecycleManager {
async trackTransactionChain(originalPOId) {
// Get original purchase order
const po = await orderfulClient.getTransaction(originalPOId);
// Find all related transactions
const relatedTxs = await orderfulClient.listTransactions({
relatedTransactionId: originalPOId
});
const chain = {
purchaseOrder: {
id: po.id,
number: po.message.purchaseOrder.purchaseOrderNumber,
status: po.deliveryStatus,
createdAt: po.createdAt
},
acknowledgment: relatedTxs.find(tx => tx.transactionType === '855'),
shipNotice: relatedTxs.find(tx => tx.transactionType === '856'),
invoice: relatedTxs.find(tx => tx.transactionType === '810')
};
return chain;
}
async getOrderStatus(poNumber) {
const chain = await this.trackTransactionChain(poNumber);
if (chain.invoice?.deliveryStatus === 'DELIVERED') {
return 'COMPLETE';
} else if (chain.shipNotice) {
return 'SHIPPED';
} else if (chain.acknowledgment) {
return 'ACKNOWLEDGED';
} else {
return 'PENDING';
}
}
}Error Handling & Resilience
Retry Strategy with Exponential Backoff
class ResilientOrderfulClient {
async sendTransactionWithRetry(transaction, maxRetries = 3) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await axios.post(
'https://api.orderful.com/v4/transactions',
transaction,
{
headers: {
'orderful-api-key': process.env.ORDERFUL_API_KEY,
'orderful-api-version': 'v4',
'Idempotency-Key': transaction.idempotencyKey
},
timeout: 30000
}
);
return response.data;
} catch (error) {
lastError = error;
// Don't retry on validation errors
if (error.response?.status === 400) {
throw error;
}
// Don't retry on auth errors
if (error.response?.status === 401 || error.response?.status === 403) {
throw error;
}
// Retry on 5xx or network errors
if (attempt < maxRetries) {
const backoffMs = Math.min(1000 * Math.pow(2, attempt), 10000);
console.log(`Retry attempt ${attempt} after ${backoffMs}ms`);
await this.sleep(backoffMs);
}
}
}
throw new Error(`Failed after ${maxRetries} attempts: ${lastError.message}`);
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}Circuit Breaker Pattern
class CircuitBreaker {
constructor(threshold = 5, timeout = 60000) {
this.failureCount = 0;
this.threshold = threshold;
this.timeout = timeout;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.nextRetryTime = null;
}
async execute(fn) {
if (this.state === 'OPEN') {
if (Date.now() < this.nextRetryTime) {
throw new Error('Circuit breaker is OPEN');
}
this.state = 'HALF_OPEN';
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failureCount = 0;
this.state = 'CLOSED';
}
onFailure() {
this.failureCount++;
if (this.failureCount >= this.threshold) {
this.state = 'OPEN';
this.nextRetryTime = Date.now() + this.timeout;
console.error(`Circuit breaker opened after ${this.failureCount} failures`);
}
}
}
// Usage
const orderfulCircuitBreaker = new CircuitBreaker(5, 60000);
async function sendTransaction(transaction) {
return await orderfulCircuitBreaker.execute(async () => {
return await orderfulClient.createTransaction(transaction);
});
}Security & Authentication
API Key Management
Best Practices:
// ✅ DO: Use environment variables
const apiKey = process.env.ORDERFUL_API_KEY;
// ✅ DO: Use secrets management services
const apiKey = await secretsManager.getSecret('orderful-api-key');
// ❌ DON'T: Hardcode in source code
const apiKey = '123abc456def'; // NEVER DO THIS
// ❌ DON'T: Commit to version control
// See .gitignore example below.gitignore:
.env
.env.local
secrets/
config/credentials.json
Webhook Signature Verification
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// Express middleware
app.use('/webhook', (req, res, next) => {
const signature = req.headers['x-orderful-signature'];
const secret = process.env.WEBHOOK_SECRET;
if (!verifyWebhookSignature(req.body, signature, secret)) {
return res.status(401).json({ error: 'Invalid signature' });
}
next();
});Rate Limiting
const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 100, // 100 requests per minute
message: 'Too many requests, please try again later'
});
app.use('/api/', apiLimiter);Performance & Scaling
Caching Strategy
const Redis = require('ioredis');
const redis = new Redis(process.env.REDIS_URL);
class CachedOrderfulClient {
async getTransactionContent(transactionId, ttl = 3600) {
const cacheKey = `tx:${transactionId}:content`;
// Try cache first
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// Fetch from API
const content = await orderfulClient.getTransaction(transactionId);
// Cache for 1 hour
await redis.setex(cacheKey, ttl, JSON.stringify(content));
return content;
}
async invalidateTransactionCache(transactionId) {
await redis.del(`tx:${transactionId}:content`);
}
}Batch Processing
async function processBatch(transactions, batchSize = 10) {
const batches = [];
for (let i = 0; i < transactions.length; i += batchSize) {
batches.push(transactions.slice(i, i + batchSize));
}
for (const batch of batches) {
await Promise.all(
batch.map(tx => processTransaction(tx))
);
}
}Monitoring & Observability
const { metrics } = require('@opentelemetry/api-metrics');
class InstrumentedOrderfulClient {
constructor() {
this.meter = metrics.getMeter('orderful-integration');
this.requestCounter = this.meter.createCounter('orderful_requests_total');
this.errorCounter = this.meter.createCounter('orderful_errors_total');
this.latencyHistogram = this.meter.createHistogram('orderful_request_duration_ms');
}
async createTransaction(transaction) {
const startTime = Date.now();
try {
const result = await orderfulClient.createTransaction(transaction);
this.requestCounter.add(1, {
transaction_type: transaction.transactionType,
status: 'success'
});
this.latencyHistogram.record(Date.now() - startTime, {
transaction_type: transaction.transactionType
});
return result;
} catch (error) {
this.errorCounter.add(1, {
transaction_type: transaction.transactionType,
error_type: error.code
});
throw error;
}
}
}Next Steps
- API Version Guide - Understanding API versions
- Mosaic Integration Guide - Detailed Mosaic implementation
- Order to Cash Workflow - Complete workflow examples
References
For more information on Orderful integration patterns:

