Примеры: от нуля до рабочей интеграции
Три полных мини‑сценария с кодом. Домен: https://example.com.
Пример 1. Только webhook (без PHP на zapex)
Цель: CRM на https://crm.example/hooks/zapex получает новые заказы.
1. Панель
- Модуль уже в
modules/Integrations/или не нужен код — достаточно любого включённого пакета с подключением (можно минимальный свой модуль безhooks.php). - Подключения модулей → создать → точка «Центральная» → Исходящие webhooks:
- URL:
https://crm.example/hooks/zapex - Секрет:
whsec_... - Хуки:
order.created(или пусто)
- URL:
2. Приёмник (Node.js)
javascript
const crypto = require('crypto');
const express = require('express');
const app = express();
app.post('/hooks/zapex', express.raw({ type: 'application/json' }), (req, res) => {
const secret = process.env.ZAPEX_WHSEC;
const body = JSON.parse(req.body.toString());
const canonical = JSON.stringify(sortKeys(body));
const sig = crypto.createHmac('sha256', secret).update(canonical).digest('hex');
if (req.get('X-Integration-Signature') !== sig) {
return res.status(401).end();
}
if (body.hook === 'order.created') {
// idempotent: queue job by body.payload.id
}
res.status(200).json({ ok: true });
});
function sortKeys(obj) {
if (Array.isArray(obj)) return obj.map(sortKeys);
if (obj && typeof obj === 'object') {
return Object.keys(obj).sort().reduce((acc, k) => {
acc[k] = sortKeys(obj[k]);
return acc;
}, {});
}
return obj;
}
3. Проверка
- Создайте тестовый заказ на точке «Центральная».
- Worker:
queue:work --queue=integrations.outbound - Лог CRM /
integrations.log
Пример 2. PHP‑модуль + handler
Цель: при order.created писать в лог и слать HTTP в CRM.
hooks.php
php
return [
'order.created' => \Modules\Integrations\MyCrm\Handlers\OrderCreatedHandler::class,
];
Handler
php
namespace Modules\Integrations\MyCrm\Handlers;
use App\Integrations\Hooks\IntegrationHookContext;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
final class OrderCreatedHandler
{
public function handle(IntegrationHookContext $context): void
{
$orderId = $context->payload['id'] ?? null;
Log::channel('integrations')->info('order.created', ['id' => $orderId]);
Http::timeout(5)->post(config('services.my_crm.url'), [
'external_id' => $orderId,
'total' => $context->payload['total'] ?? null,
]);
}
}
Симуляция без заказа
bash
php artisan integrations:hooks:simulate order.created \
--package=MyCrm \
--payload='{"id":999,"pickup_point_id":1,"total":1500}'
Пример 3. REST: смена статуса позиции из CRM
Цель: CRM выставляет «Выдано» через API.
1. Настройка API
Настройка API → ключ с ролью, где есть orders.write_status, точка = нужный ПВЗ.
2. Запрос
bash
curl -sS -X POST "https://example.com/api/integrations/v1/orders/42/items/7/status" \
-H "Authorization: Bearer my-id:my-secret" \
-H "Content-Type: application/json" \
-d '{"status_code":"item.issued"}'
3. Ожидание в CRM
Подпишите webhook на order.item_status_changed — при смене с сайта придёт подтверждение с status_semantic.
Пример 4. Inbound: CRM пушит событие в магазин
bash
BODY='{"action":"ping"}'
SECRET='inbound-secret-from-instance-credentials'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')
curl -sS -D- -X POST \
"https://example.com/api/integrations/v1/integrations/inbound/MyCrm/3" \
-H "Content-Type: application/json" \
-H "X-Integration-Signature: $SIG" \
-d "$BODY"
Ожидается: HTTP/1.1 202 и {"accepted":true}.
Пример 5. Проверка подписи outbound (PHP)
php
$raw = file_get_contents('php://input');
$body = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
$secret = getenv('OUTBOUND_SECRET');
// Как в ядре:
ksort_recursive($body);
$canonical = json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$expected = hash_hmac('sha256', $canonical, $secret);
function ksort_recursive(array &$arr): void {
ksort($arr);
foreach ($arr as &$v) {
if (is_array($v)) ksort_recursive($v);
}
}
Используйте весь объект {hook, payload, sent_at}, не только payload.
Пример 6. Модуль с instance_form и bulk
- Manifest:
instance_form,external_system,inbound_webhook,hooks. - Подключения → Создать → Все активные точки → заполнить форму модуля (креды, маппинг).
- В списке — по одной строке на склад; inbound URL с разным
{instance}. - Раздел Интеграции отдельно не заполняется вручную, если служебные записи создаёт bulk.
См. Подключения модулей.