Order Processing Automation via Webhook Chains
Webhook automation transforms manual order operations into event-driven chains: order created → notify warehouse → update CRM → send customer tracking. Each step is independent, fault-tolerant, and logged.
Event chain architecture
Store (event) → Webhook → Queue → Handler → External systems
↓
Dead Letter Queue (on errors)
Key principle: webhook is accepted instantly, processed asynchronously. Endpoint returns 200 OK in milliseconds, real work — in queue.
Laravel: receiving and queuing
// routes/api.php
Route::post('/webhooks/orders', [OrderWebhookController::class, 'handle'])
->middleware('webhook.signature');
// app/Http/Controllers/OrderWebhookController.php
class OrderWebhookController extends Controller
{
public function handle(Request $request): JsonResponse
{
// Queue immediately — response < 50ms
ProcessOrderWebhook::dispatch(
$request->input('event'),
$request->all()
)->onQueue('webhooks');
return response()->json(['status' => 'queued'], 200);
}
}
Webhook signature verification
// app/Http/Middleware/VerifyWebhookSignature.php
class VerifyWebhookSignature
{
public function handle(Request $request, Closure $next): mixed
{
$signature = $request->header('X-Webhook-Signature');
$payload = $request->getContent();
$secret = config('webhooks.secret');
$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expected, $signature ?? '')) {
return response()->json(['error' => 'Invalid signature'], 403);
}
return $next($request);
}
}
Main handler with action chain
// app/Jobs/ProcessOrderWebhook.php
class ProcessOrderWebhook implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 3;
public int $backoff = 60; // seconds between retries
public function __construct(
private string $event,
private array $payload
) {}
public function handle(OrderWebhookPipeline $pipeline): void
{
match ($this->event) {
'order.created' => $pipeline->onOrderCreated($this->payload),
'order.paid' => $pipeline->onOrderPaid($this->payload),
'order.shipped' => $pipeline->onOrderShipped($this->payload),
'order.cancelled' => $pipeline->onOrderCancelled($this->payload),
default => Log::warning("Unknown webhook event: {$this->event}"),
};
}
public function failed(\Throwable $e): void
{
Log::error("Webhook processing failed", [
'event' => $this->event,
'error' => $e->getMessage(),
'payload' => $this->payload,
]);
// Alert on Slack/Telegram on final failure
Notification::route('slack', config('webhooks.slack_url'))
->notify(new WebhookFailedNotification($this->event, $e));
}
}
Order processing pipeline
// app/Services/OrderWebhookPipeline.php
class OrderWebhookPipeline
{
public function onOrderCreated(array $payload): void
{
$order = $this->findOrCreateOrder($payload);
// Run independent tasks in parallel
Bus::batch([
new SyncOrderToCrm($order),
new NotifyWarehouse($order),
new SendOrderConfirmation($order),
new UpdateInventoryReservation($order),
])->allowFailures()->dispatch();
}
public function onOrderPaid(array $payload): void
{
$order = Order::findByExternalId($payload['order']['id']);
// Sequential chain: each step depends on previous
Bus::chain([
new MarkOrderAsPaid($order),
new GenerateInvoice($order),
new TriggerFulfillment($order),
new SendPaymentConfirmation($order),
])->dispatch();
}
public function onOrderShipped(array $payload): void
{
$order = Order::findByExternalId($payload['order']['id']);
$tracking = $payload['shipment']['tracking_number'] ?? null;
Bus::chain([
new UpdateOrderTracking($order, $tracking),
new SendShippingNotification($order, $tracking),
new UpdateCrmOrderStatus($order, 'shipped'),
])->dispatch();
}
}
Synchronization with 1C/CRM
// app/Jobs/SyncOrderToCrm.php
class SyncOrderToCrm implements ShouldQueue
{
use Queueable;
public int $tries = 5;
public array $backoff = [30, 60, 120, 300, 600]; // Exponential backoff
public function __construct(private Order $order) {}
public function handle(AmoCrmClient $crm): void
{
$lead = $crm->leads()->create([
'name' => "Order #{$this->order->increment_id}",
'price' => $this->order->grand_total,
'status_id' => config('amocrm.new_order_status'),
'custom_fields_values' => [
['field_code' => 'ORDER_ID', 'values' => [['value' => $this->order->id]]],
['field_code' => 'CUSTOMER_EMAIL', 'values' => [['value' => $this->order->customer_email]]],
],
]);
$this->order->update(['crm_lead_id' => $lead['id']]);
}
}
Outgoing webhooks to partners
Some integrations require sending webhooks on order status change:
// app/Listeners/OrderStatusChangedListener.php
class OrderStatusChangedListener
{
public function handle(OrderStatusChanged $event): void
{
$webhooks = WebhookSubscription::where('event', 'order.status_changed')
->where('is_active', true)
->get();
foreach ($webhooks as $webhook) {
SendOutgoingWebhook::dispatch($webhook, [
'event' => 'order.status_changed',
'order' => [
'id' => $event->order->id,
'status' => $event->order->status,
],
'timestamp' => now()->toIso8601String(),
])->onQueue('outgoing-webhooks');
}
}
}
// app/Jobs/SendOutgoingWebhook.php
class SendOutgoingWebhook implements ShouldQueue
{
public int $tries = 3;
public function handle(Http $http): void
{
$payload = json_encode($this->payload);
$signature = hash_hmac('sha256', $payload, $this->webhook->secret);
$response = $http->withHeaders([
'Content-Type' => 'application/json',
'X-Webhook-Signature' => 'sha256=' . $signature,
])->timeout(10)->post($this->webhook->url, $this->payload);
if ($response->failed()) {
throw new \RuntimeException(
"Webhook delivery failed: HTTP {$response->status()}"
);
}
$this->webhook->update(['last_triggered_at' => now()]);
}
}
Monitoring and logging
// Table for webhook history
Schema::create('webhook_logs', function (Blueprint $table) {
$table->id();
$table->string('event');
$table->string('direction'); // incoming / outgoing
$table->json('payload');
$table->integer('status_code')->nullable();
$table->text('error')->nullable();
$table->integer('attempt')->default(1);
$table->timestamps();
});
Monitoring via Laravel Horizon: dashboard shows queue delay, failed task count, and processing speed.
Development timeline
The entire workflow takes 2-4 weeks, with 3-5 days for monitoring setup.







