Integration Patterns & Architecture

This guide provides comprehensive integration patterns for building robust Orderful integrations, including architecture decisions, communication patterns, and implementation strategies

Table of Contents

  1. Communication Patterns
  2. Architecture Patterns
  3. Transaction Lifecycle Management
  4. Error Handling & Resilience
  5. Security & Authentication
  6. 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:

  1. Navigate to Settings → Communication Channels
  2. Click Create New and select HTTP
  3. Enter your endpoint URL (e.g., https://api.yourcompany.com/orderful/webhook)
  4. 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 minutes

Architecture 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


References

For more information on Orderful integration patterns: