Настройка автоматизации обработки заказов через Webhook-цепочки
Webhook-автоматизация превращает ручные операции с заказами в событийно-управляемые цепочки: заказ создан → уведомить склад → обновить CRM → отправить покупателю трекинг. Каждый шаг независим, отказоустойчив и логируется.
Архитектура событийных цепочек
Магазин (событие) → Webhook → Queue → Handler → Внешние системы
↓
Dead Letter Queue (при ошибках)
Ключевой принцип: webhook принимается мгновенно, обрабатывается асинхронно. Endpoint возвращает 200 OK за миллисекунды, реальная работа — в очереди.
Laravel: приём и постановка в очередь
// 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
{
// Немедленно в очередь — ответ за < 50ms
ProcessOrderWebhook::dispatch(
$request->input('event'),
$request->all()
)->onQueue('webhooks');
return response()->json(['status' => 'queued'], 200);
}
}
Проверка подписи webhook
// 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);
}
}
Основной обработчик с цепочкой действий
// app/Jobs/ProcessOrderWebhook.php
class ProcessOrderWebhook implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 3;
public int $backoff = 60; // секунд между попытками
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,
]);
// Уведомление в Slack/Telegram при окончательном провале
Notification::route('slack', config('webhooks.slack_url'))
->notify(new WebhookFailedNotification($this->event, $e));
}
}
Pipeline обработки заказа
// app/Services/OrderWebhookPipeline.php
class OrderWebhookPipeline
{
public function onOrderCreated(array $payload): void
{
$order = $this->findOrCreateOrder($payload);
// Параллельный запуск независимых задач
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']);
// Последовательная цепочка: каждый шаг зависит от предыдущего
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();
}
}
Синхронизация с 1С/CRM через очередь
// app/Jobs/SyncOrderToCrm.php
class SyncOrderToCrm implements ShouldQueue
{
use Queueable;
public int $tries = 5;
public array $backoff = [30, 60, 120, 300, 600]; // Экспоненциальный backoff
public function __construct(private Order $order) {}
public function handle(AmoCrmClient $crm): void
{
$lead = $crm->leads()->create([
'name' => "Заказ #{$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']]);
}
}
Исходящие webhooks к партнёрам
Некоторые интеграции требуют отправки webhook при изменении статуса заказа:
// 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()]);
}
}
Мониторинг и логирование
// Таблица для истории webhook
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();
});
Мониторинг через Laravel Horizon: дашборд показывает задержку очереди, количество провалившихся задач и скорость обработки.
Типовые цепочки
| Событие | Цепочка действий |
|---|---|
| Заказ создан | Резервирование → CRM → Уведомление → Склад |
| Оплачен | Статус → Инвойс → Фулфилмент → SMS |
| Отправлен | Трекинг → Email → CRM → Аналитика |
| Отменён | Возврат резерва → CRM → Возврат оплаты → Email |
Весь поток занимает 2-4 недели реализации и 3-5 дней настройки мониторинга.







