Питання на співбесіді з API
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
100 питань
Запит складається з рядка запиту, заголовків і (необов'язково) тіла:
POST /api/orders HTTP/1.1
Host: shop.example.com
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJhbGciOi...
Idempotency-Key: 8f14e45f-ceea-467f-a0d6-2a1e6c0c4f11
{"product_id": 42, "qty": 2}
- метод (
GET,POST,PATCH...) - що зробити; - шлях і рядок запиту - з яким ресурсом;
- заголовки - метадані: формат, автентифікація, кешування;
- тіло - дані (для
GETтіла зазвичай немає).
Відповідь - рядок статусу, заголовки, тіло:
HTTP/1.1 201 Created
Content-Type: application/json
Location: /api/orders/1057
Cache-Control: no-store
{"data": {"id": 1057, "status": "new"}}
Заголовки, які варто знати для API:
| Заголовок | Навіщо |
|---|---|
Content-Type |
формат тіла, яке надсилається |
Accept |
формат, який клієнт хоче отримати |
Authorization |
облікові дані (Bearer-токен) |
Location |
адреса створеного ресурсу (з 201) чи статусу операції (з 202) |
Cache-Control, ETag |
кешування й умовні запити |
Retry-After |
коли повторити (з 429, 503) |
X-Request-Id / traceparent |
наскрізний ідентифікатор для логів і трасування |
Типові помилки:
Content-Typeне відповідає тілу - сервер не розбере JSON, надісланий якtext/plain;- відсутній
Accept: application/jsonу запитах до Laravel - при помилці валідації замість JSON 422 прийде редирект на попередню сторінку; - статус 200 з
{"error": ...}у тілі - клієнти, проксі й моніторинг орієнтуються на код статусу, тож помилка має бути помилкою на рівні HTTP; - власні заголовки з префіксом
X-- застарілий звичай (RFC 6648); нові заголовки називають без нього.
Налагодження: curl -i показує заголовки відповіді, curl -v - і запиту; у браузері - вкладка Network.
Два заголовки, які часто плутають, описують різні напрямки:
Content-Type- формат тіла цього повідомлення. У запиті - що надсилає клієнт, у відповіді - що повертає сервер;Accept- формат, який клієнт готовий отримати у відповідь.
POST /api/reports
Content-Type: application/json
Accept: text/csv
{"from": "2026-09-01", "to": "2026-09-30"}
Клієнт надсилає JSON, а хоче отримати CSV.
Узгодження вмісту (content negotiation) - сервер обирає формат відповіді за Accept:
Accept: application/json;q=1.0, text/csv;q=0.5
q - вага переваги від 0 до 1. Сервер повертає найкращий з підтримуваних форматів, а якщо не підтримує жоден - 406 Not Acceptable. Якщо сервер не розуміє формат тіла запиту - 415 Unsupported Media Type.
Інші заголовки узгодження:
Accept-Language- мова (повідомлення помилок, перекладені поля);Accept-Encoding- стиснення (gzip,br,zstd);- у відповіді -
Vary: Accept, Accept-Language, щоб кеші й CDN зберігали окремі копії для різних варіантів.
Медіатипи для API:
application/json- основний;application/problem+json- помилки за RFC 9457;application/vnd.api+json- JSON:API;multipart/form-data- завантаження файлів;- власні «вендорні» типи з версією (
application/vnd.shop.v2+json) - один зі способів версіонування.
У Laravel:
$request->expectsJson()/wantsJson()перевіряютьAccept- від нього залежить, чи поверне Laravel помилки валідації й винятки як JSON;$request->isJson()- чи тіло запиту JSON (заContent-Type);$request->getAcceptableContentTypes()- список з урахуванням ваг.
Практичний підсумок: API-клієнти мають завжди надсилати Accept: application/json, а сервер - завжди виставляти правильний Content-Type відповіді з кодуванням (application/json для JSON вважається UTF-8 за специфікацією).
Пагінація зі зміщенням (offset) - номер сторінки:
GET /api/posts?page=3&per_page=20
SELECT * FROM posts ORDER BY created_at DESC LIMIT 20 OFFSET 40;
Курсорна пагінація - «продовжити після цього запису»:
GET /api/posts?cursor=eyJjcmVhdGVkX2F0IjoiMjAyNi0wOS0zMCJ9
SELECT * FROM posts WHERE created_at < '2026-09-30 12:00:00' ORDER BY created_at DESC LIMIT 20;
Курсор - закодоване значення ключа сортування останнього елемента попередньої сторінки.
Порівняння:
| Зміщення | Курсор | |
|---|---|---|
| перехід на довільну сторінку | так | ні, лише вперед/назад |
| загальна кількість сторінок | так (потрібен COUNT(*)) |
зазвичай ні |
| швидкість на далеких сторінках | падає: база читає й відкидає OFFSET рядків |
стабільна: пошук за індексом |
| нові записи під час гортання | дублікати й пропуски | коректно |
Проблема зміщення з даними, що змінюються: поки користувач на сторінці 2, з'явилося 5 нових постів. Сторінка 3 тепер починається на 5 записів «раніше» - частину постів він побачить двічі. Курсор прив'язаний до значення, а не до позиції, і такої проблеми не має.
У Laravel:
Post::latest()->paginate(20); // зміщення + COUNT(*), номери сторінок
Post::latest()->simplePaginate(20); // зміщення без COUNT(*), лише «далі/назад»
Post::latest()->cursorPaginate(20); // курсор
cursorPaginate вимагає, щоб сортування було за унікальною комбінацією колонок (зазвичай додають id як останній ключ) і щоб для неї був індекс.
Що обрати:
- адмінки, таблиці з переходом на сторінку N, невеликі обсяги - зміщення;
- стрічки, нескінченний скрол, мобільні застосунки, синхронізація й експорт великих обсягів - курсор.
У відповіді API варто віддавати посилання на наступну сторінку (links.next чи заголовок Link), а не змушувати клієнта збирати URL самостійно - тоді механізм пагінації можна змінити без зміни клієнтів.
HEAD - те саме, що GET, але без тіла відповіді: сервер повертає лише статус і заголовки.
HEAD /api/exports/2026-09.csv
HTTP/1.1 200 OK
Content-Type: text/csv
Content-Length: 48211337
Last-Modified: Wed, 01 Oct 2026 03:00:00 GMT
ETag: "a1b2c3"
Навіщо:
- дізнатися розмір файлу перед завантаженням (смуга прогресу, перевірка місця);
- перевірити, чи ресурс існує чи змінився (
ETag,Last-Modified) без передачі вмісту; - перевірка посилань - сканери битих посилань і моніторинг доступності.
Заголовки відповіді на HEAD мають бути такими самими, як для GET. У Laravel маршрути GET автоматично відповідають і на HEAD - фреймворк просто відкидає тіло.
OPTIONS - запит про можливості ресурсу: які методи підтримуються.
OPTIONS /api/orders/42
HTTP/1.1 204 No Content
Allow: GET, PATCH, DELETE
Головне застосування на практиці - попередній запит CORS (preflight). Перед «непростим» запитом з іншого джерела (методи PUT/PATCH/DELETE, заголовок Authorization, Content-Type: application/json) браузер сам надсилає OPTIONS:
OPTIONS /api/orders/42
Origin: https://app.example.com
Access-Control-Request-Method: PATCH
Access-Control-Request-Headers: authorization, content-type
Сервер відповідає дозволами (Access-Control-Allow-*), і лише потім іде справжній запит. У Laravel це робить middleware HandleCors з config/cors.php.
Що варто знати:
- обидва методи безпечні й ідемпотентні - не повинні змінювати стан;
- preflight - додатковий запит на кожен «непростий» запит з іншого джерела.
Access-Control-Max-Ageдозволяє браузеру кешувати дозвіл і не повторюватиOPTIONSщоразу; - автентифікація для
OPTIONS: браузер не надсилаєAuthorizationу preflight. Якщо middleware автентифікації відхиляєOPTIONSз 401, CORS-запити ламаються - preflight має оброблятися до автентифікації (як це й робить глобальнийHandleCors); 405 Method Not Allowedразом із заголовкомAllow- правильна відповідь на непідтримуваний метод.
JSON добре стискається: повторювані ключі й структура дають зменшення у 5-10 разів. Для мобільних клієнтів і великих списків це суттєва економія часу й трафіку.
Як це працює:
GET /api/products
Accept-Encoding: zstd, br, gzip
HTTP/1.1 200 OK
Content-Type: application/json
Content-Encoding: br
Vary: Accept-Encoding
- клієнт перелічує в
Accept-Encodingалгоритми, які розуміє; - сервер обирає один, стискає тіло й повідомляє про це в
Content-Encoding; - клієнт (браузер,
fetch, HTTP-клієнт) розпаковує автоматично.
Vary: Accept-Encoding обов'язковий для кешів і CDN - інакше стиснена копія може дістатися клієнту, що її не розуміє.
Алгоритми:
| Алгоритм | Особливість |
|---|---|
gzip |
підтримується всюди, швидкий, середнє стиснення |
br (Brotli) |
краще стиснення за gzip, особливо для тексту; підтримка в усіх сучасних браузерах |
zstd (Zstandard) |
дуже швидке стиснення й розпакування при хорошому коефіцієнті; підтримується в Chrome і Firefox, Safari - з 2025 року |
Сервер обирає найкращий з того, що підтримує клієнт, і зазвичай лишає gzip як запасний варіант.
Де вмикати: не в PHP, а на рівні вебсервера чи CDN - Nginx (gzip, модуль brotli), Caddy (encode zstd gzip), Cloudflare. Там стиснення ефективніше й не займає воркери PHP.
Що варто знати:
- малі відповіді не стискають: для тіла в кількасот байтів накладні витрати більші за виграш (типовий поріг - 1 КБ);
- вже стиснене (зображення, архіви, PDF) повторно не стискають;
- рівень стиснення - компроміс з процесором: для динамічних відповідей - середній рівень, максимальний - лише для статики, стисненої заздалегідь;
- BREACH: стиснення відповідей, що містять і секрет (CSRF-токен), і дані, підконтрольні атакувальнику, на HTTPS дає змогу вгадувати секрет за розміром відповіді. Для JSON API з токенами в заголовках, а не в тілі, ризик невеликий, але про атаку варто знати;
- тіло запиту клієнти зазвичай не стискають -
Content-Encodingу запиті сервер має підтримувати окремо.
Перевірка: curl -H 'Accept-Encoding: br' -I https://api.example.com/products - у відповіді має бути Content-Encoding.
OpenAPI (раніше Swagger) - стандартний машиночитний формат опису HTTP API у YAML чи JSON: ендпойнти, параметри, тіла запитів і відповідей, коди статусу, автентифікація.
openapi: 3.1.0
info:
title: Vacancies API
version: 1.0.0
paths:
/vacancies/{id}:
get:
summary: Отримати вакансію
parameters:
- name: id
in: path
required: true
schema: { type: integer }
responses:
'200':
description: Вакансія
content:
application/json:
schema: { $ref: '#/components/schemas/Vacancy' }
'404':
description: Не знайдено
components:
schemas:
Vacancy:
type: object
required: [id, title]
properties:
id: { type: integer }
title: { type: string }
salary_from: { type: [integer, 'null'] }
Що дає специфікація, крім документації:
- інтерактивна документація - Swagger UI, Redoc, Scalar, Stoplight Elements з кнопкою «спробувати запит»;
- генерація клієнтів - типізовані SDK для TypeScript, PHP, мобільних застосунків;
- моки - фронтенд працює з фейковим сервером до готовності бекенду;
- тести й валідація - перевірка, що реальні відповіді відповідають опису;
- лінтинг - автоматична перевірка стилю й помилок у дизайні;
- виявлення змін, що ламають клієнтів, - порівняння версій специфікації в CI;
- імпорт у Postman, Insomnia, Bruno, API-шлюзи.
Версії:
- 3.0 - найпоширеніша в старих інструментах;
- 3.1 (2021) - повна сумісність із JSON Schema 2020-12;
- 3.2 (вересень 2025) - зворотно сумісне розширення 3.1: потокові медіатипи (SSE, JSON Lines), нові поля для тегів, метод
QUERYтощо.
Для нових проєктів - 3.1 чи 3.2, з перевіркою, що потрібні інструменти їх підтримують.
Головна цінність - єдине джерело правди про контракт між бекендом і клієнтами, замість вікі-сторінок, які розходяться з кодом через тиждень.
У Laravel специфікацію найчастіше генерують з коду (Scramble, Scribe), а не пишуть вручну.
Code-first - спершу пишеться код, специфікація генерується з нього (атрибутами, аналізом коду):
контролери й ресурси Laravel → Scramble/Scribe → openapi.json → документація
Design-first - спершу пишеться специфікація (контракт), її обговорюють і погоджують, а потім за нею пишуть код і клієнтів:
openapi.yaml (рев'ю) → моки для фронтенду → реалізація бекенду → перевірка відповідності
Переваги code-first:
- специфікація не розходиться з кодом - вона з нього й береться;
- швидкий старт, мало додаткової роботи;
- зручно для невеликих команд, де бекенд і фронтенд пишуть ті самі люди.
Недоліки code-first:
- дизайн API формується реалізацією: назви полів і структура відповідей відображають внутрішню модель (колонки таблиць), а не потреби клієнтів;
- обговорити API можна лише після того, як його написано;
- генератор бачить не все: деталі на кшталт можливих помилок чи прикладів доводиться доповнювати.
Переваги design-first:
- контракт - до коду: фронтенд, мобільна команда й партнери погоджують API заздалегідь і працюють паралельно з моками;
- кращий дизайн: API проєктують з боку споживача, з рев'ю, як код;
- генерація серверних заготовок і валідація запитів за специфікацією.
Недоліки design-first:
- специфікацію треба підтримувати вручну - і перевіряти, що реалізація їй відповідає (тести контракту, валідація відповідей у тестах), інакше вона застаріває;
- більше процесу й інструментів.
Що обирають на практиці:
- внутрішнє API одного продукту (Laravel + SPA однієї команди) - code-first зі Scramble: мінімум зусиль, завжди актуально;
- публічне API, партнерські інтеграції, кілька команд-споживачів - design-first або гібрид: дизайн погоджується в специфікації, а згенерована з коду специфікація порівнюється з погодженою в CI;
- будь-який підхід виграє від лінтингу специфікації і перевірки змін, що ламають клієнтів, у CI.
Докладніше в документації: Документація OpenAPI для початківців
Scramble генерує специфікацію OpenAPI з коду Laravel автоматично, без анотацій над кожним методом. Він аналізує:
- маршрути - шляхи, методи, параметри маршруту й прив'язку моделей;
- валідацію - правила з Form Request чи
$request->validate()стають описом тіла запиту й параметрів (required|email|max:255→ обов'язковий рядок формату email); - відповіді - API-ресурси (
JsonResource), повернені моделі, пагінацію,response()->json(...); - помилки - 422 для валідації, 404 для прив'язки моделей, 403 для авторизації, 401 для автентифікації.
composer require dedoc/scramble
Після встановлення документація доступна за адресою /docs/api (інтерфейс на основі Stoplight Elements), а сама специфікація - /docs/api.json.
Доповнення там, де аналізу коду недостатньо:
/**
* Список вакансій.
*
* Повертає опубліковані вакансії, найновіші першими.
*/
public function index(IndexVacancyRequest $request)
{
// ...
}
PHPDoc-коментар стає описом операції. Для складних випадків - атрибути Scramble й розширення.
Що варто налаштувати:
- доступ: за замовчуванням документація відкрита лише в локальному оточенні, у продакшені доступ визначає gate
viewApiDocs; - які маршрути документувати (за замовчуванням - з префіксом
api); - автентифікація: опис схеми (Bearer-токен Sanctum), щоб кнопка «спробувати» працювала;
- експорт:
php artisan scramble:exportзаписує специфікацію у файл - для генерації клієнтів і перевірок у CI.
Альтернатива - Scribe: генерує документацію й колекції Postman, бере опис з анотацій і може робити справжні запити до ендпойнтів, щоб отримати приклади відповідей.
Обмеження автоматичної генерації:
- точність залежить від коду: динамічні масиви в
toArray(), умовна логіка, дані з сервісів можуть описатися неточно - результат треба переглядати; - якість опису - на вас: назви полів, приклади, пояснення бізнес-правил генератор не придумає;
- вбудовані JSON:API-ресурси Laravel 13 - новий формат; варто перевірити, що версія генератора їх підтримує.
Laravel має набір перевірок для JSON-відповідей. Головне - обирати перевірку під те, що саме гарантує API.
it('returns a vacancy', function () {
$vacancy = Vacancy::factory()->create(['title' => 'Laravel Developer', 'salary_from' => 3000]);
$this->getJson("/api/vacancies/{$vacancy->id}")
->assertOk()
->assertJsonPath('data.title', 'Laravel Developer')
->assertJsonPath('data.salary_from', 3000)
->assertJsonStructure([
'data' => ['id', 'title', 'salary_from', 'company' => ['id', 'name']],
]);
});
Основні методи:
| Метод | Що перевіряє |
|---|---|
assertJson([...]) |
відповідь містить ці дані (інші поля дозволені) |
assertExactJson([...]) |
відповідь дорівнює цьому JSON |
assertJsonPath('data.title', '...') |
значення за шляхом |
assertJsonStructure([...]) |
наявність ключів (без значень) |
assertExactJsonStructure([...]) |
ключі - і жодних зайвих |
assertJsonCount(20, 'data') |
кількість елементів |
assertJsonMissingPath('data.password') |
поля немає |
assertJsonValidationErrors(['email']) |
помилки валідації для полів |
Методи запиту: getJson, postJson, patchJson, deleteJson - автоматично додають Accept: application/json, тож помилки валідації приходять як JSON 422, а не редирект.
Що варто перевіряти в API-тестах:
- контракт: структура відповіді й типи - те, на що покладаються клієнти;
- приховані поля:
assertJsonMissingPath('data.password_hash')- захист від випадкового витоку при зміні ресурсу; - коди статусу для кожного сценарію: 201 при створенні, 422 при помилках, 403 для чужого ресурсу, 404 для неіснуючого;
- авторизацію: запит від іншого користувача (
actingAs($stranger)) - найчастіше забута перевірка; - пагінацію й фільтри:
assertJsonCount, перевірка, що фільтр справді відсікає записи.
Пастки:
assertJsonне перевіряє відсутність полів - нове випадкове поле в ресурсі тест не помітить. Для захисту контракту -assertExactJsonStructureабо перевірка відповідності специфікації OpenAPI;- типи:
assertJsonPath('data.price', 100)і'100.00'- різні значення; рішення про формат грошей має бути зафіксоване тестом; - дати порівнюють у форматі, який віддає API (ISO 8601 з поясом), а не як об'єкти Carbon.
Для складних перевірок - fluent-API assertJson(fn (AssertableJson $json) => ...).
Згенерований перелік ендпойнтів з параметрами - лише довідник. Розробник, що інтегрується з API, має відповіді на питання «з чого почати» і «що робити, коли щось пішло не так».
1. Швидкий старт: як отримати ключ, перший запит, який можна скопіювати й виконати за хвилину:
curl https://api.example.com/v1/vacancies?city=kyiv \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"
2. Автентифікація: як отримати й оновити токен, термін дії, області доступу (scopes).
3. Приклади - для кожного запиту й відповіді. Реалістичні дані, а не "string" і 0. В OpenAPI - поля example/examples у схемах і медіатипах. Кілька прикладів для різних сценаріїв (вакансія із зарплатою і без).
4. Помилки - не менш детально, ніж успіх:
- формат помилки (Problem Details чи власний) з прикладами;
- коди помилок бізнес-логіки й що з ними робити (
insufficient_balance- поповнити рахунок); - які помилки тимчасові й можна повторити (429, 503), а які ні.
5. Загальні правила API - один раз для всіх ендпойнтів:
- пагінація, фільтрація, сортування;
- формати дат, грошей, ідентифікаторів;
- ліміти частоти й заголовки, що їх показують;
- ідемпотентність і повтори;
- версіонування й політика змін.
6. Сценарії (гайди): типові задачі, що складаються з кількох запитів - «створити замовлення й оплатити», «синхронізувати каталог». Довідник ендпойнтів їх не пояснює.
7. Журнал змін (changelog) і дати застарівання.
8. SDK і колекції: офіційні клієнти, колекція Postman/Bruno, посилання на специфікацію OpenAPI для генерації власних клієнтів.
Якість, яку легко перевірити:
- новий розробник робить перший успішний запит без запитань до команди;
- кожен ендпойнт має приклад відповіді й перелік можливих помилок;
- приклади в документації перевіряються автоматично (контрактні тести, валідація за специфікацією) - інакше вони застаріють першими.
Інтерактивність: кнопка «спробувати» в документації (Swagger UI, Scalar, Stoplight Elements) з тестовим оточенням (sandbox) пришвидшує інтеграцію - але ніколи з продакшен-даними.
Докладніше в документації: Документація OpenAPI для початківців
Три поширені стилі API розв'язують різні задачі.
REST - ресурси за URL і стандартні методи HTTP:
GET /api/orders/42
GET /api/orders/42/items
- сильні сторони: простота, HTTP-кешування (CDN,
ETag), зрозумілі коди відповіді, будь-який клієнт (навітьcurl); - слабкі: клієнт отримує фіксовану форму відповіді - або зайві поля (overfetching), або кілька запитів, щоб зібрати екран (underfetching).
GraphQL - один ендпойнт, клієнт сам описує, які поля потрібні:
query {
order(id: 42) { number total items { name qty } customer { name } }
}
- сильні сторони: один запит на екран, сувора схема з типами, зручно для кількох клієнтів з різними потребами (веб, мобільний застосунок);
- слабкі: складніше кешування (зазвичай
POSTна один URL), ризик дорогих запитів, N+1 на сервері, складніші авторизація й обмеження частоти.
gRPC - виклик віддалених процедур поверх HTTP/2 з бінарним форматом Protocol Buffers і згенерованими клієнтами:
- сильні сторони: швидкість і компактність, суворий контракт у
.proto, двобічний стримінг; - слабкі: браузер не викликає gRPC напряму (потрібен gRPC-Web чи шлюз), бінарні повідомлення важче налагоджувати.
Як обирати:
| Ситуація | Стиль |
|---|---|
| публічне API, інтеграції партнерів, вебхуки | REST |
| багато клієнтів з різними екранами, складні зв'язані дані | GraphQL |
| внутрішні сервіси між собою, високе навантаження, стримінг | gRPC |
| прості дії на кшталт «надіслати лист» | RPC поверх HTTP (POST /api/send-invoice) |
Практичне зауваження: для типового Laravel-застосунку з одним фронтендом REST (чи Inertia без окремого API) майже завжди простіший. GraphQL і gRPC окупаються, коли їхні сильні сторони справді потрібні, - а не «бо модно».
Схема - контракт API: які типи є, які поля в них і що можна запитати. Пишеться мовою SDL:
type User {
id: ID!
name: String!
email: String
posts: [Post!]!
}
type Post {
id: ID!
title: String!
author: User!
}
type Query {
user(id: ID!): User
posts(first: Int = 10): [Post!]!
}
type Mutation {
createPost(title: String!, body: String!): Post!
}
! - поле не може бути null. [Post!]! - список, який сам не null і не містить null.
Запит (query) - читання. Клієнт вибирає лише потрібні поля, включно з вкладеними:
query {
user(id: 7) {
name
posts { title }
}
}
Відповідь повторює форму запиту: { "data": { "user": { "name": "Оля", "posts": [...] } } }.
Мутація (mutation) - зміна даних. Синтаксис як у запиту, але виконується послідовно, а результат - змінений об'єкт:
mutation {
createPost(title: "Привіт", body: "...") { id title }
}
Підписка (subscription) - потік подій у реальному часі, зазвичай через WebSocket.
Резолвер - функція на сервері, що повертає значення поля. Для кожного поля в запиті сервер викликає його резолвер. Простим полям (name) достатньо значення з батьківського об'єкта, а для зв'язків (posts) резолвер іде в базу.
Відмінності від REST, які варто пам'ятати:
- одна адреса (
/graphql) і зазвичай методPOST; - помилки не через коди HTTP: відповідь часто має статус 200, а помилки лежать у масиві
errorsпоряд із частковими даними вdata; - інтроспекція: схему можна запитати через сам API - на цьому побудовані автодоповнення в GraphiQL і генератори типів для клієнтів.
У Laravel найпоширеніший пакет - Lighthouse: схема описується в .graphql-файлі, а директиви (@all, @find, @paginate, @create) прив'язують поля до моделей Eloquent без ручних резолверів.
Вебхук - HTTP-запит, який ваш сервіс надсилає на URL клієнта, коли щось сталося: замовлення оплачене, вакансію опубліковано. Клієнт не опитує API, а отримує подію сам.
Що надсилати:
{
"id": "evt_01J9Z3K8",
"type": "order.paid",
"created_at": "2026-10-04T10:15:00Z",
"api_version": "2026-09-01",
"data": {
"object": { "id": 42, "status": "paid", "total": "1250.00", "currency": "UAH" }
}
}
idподії - унікальний: одержувач за ним відкидає дублікати;type- назва події у форматіресурс.дія; клієнт підписується лише на потрібні;- час події - щоб одержувач міг розібратися з порядком;
- версія формату - щоб змінювати структуру, не ламаючи наявних інтеграцій;
- дані: або повний знімок об'єкта («товстий» вебхук), або лише ідентифікатор («тонкий», одержувач сам запитує актуальний стан через API).
Обов'язкові складники надійного вебхука:
- підпис запиту (HMAC з секретом одержувача) і мітка часу - щоб одержувач перевірив, що запит від вас і не повторений;
- HTTPS для URL одержувача;
- доставка «щонайменше один раз»: при помилці чи тайм-ауті - повторні спроби з наростаючою затримкою. Одержувач має бути готовим до дублікатів;
- швидка відповідь: одержувач підтверджує прийом кодом 2xx одразу, а обробляє асинхронно. Документуйте тайм-аут (кілька секунд);
- журнал доставок у кабінеті: які події пішли, з яким кодом відповіді, кнопка «надіслати ще раз».
Відправлення в Laravel - лише через чергу. HTTP-запит до чужого сервера в обробнику запиту користувача - затримки й падіння, якщо одержувач повільний чи недоступний. Готове рішення - пакет spatie/laravel-webhook-server: черга, підпис, повтори з затримкою, події про невдалі доставки.
Документація для одержувачів: перелік типів подій із прикладами, як перевіряти підпис, політика повторів, тестові події з кабінету.
REST працює за схемою «запит - відповідь»: клієнт питає, сервер відповідає. Коли клієнту потрібно дізнаватися про зміни одразу, без постійного опитування, використовують постійне з'єднання.
Варіанти:
- опитування (polling) - запит раз на N секунд. Найпростіше, але створює багато порожніх запитів і затримку до N секунд;
- Server-Sent Events (SSE) - однобічний потік від сервера до клієнта через звичайне HTTP-з'єднання. Автоматичне перепідключення вбудоване в браузер. Добре для сповіщень, прогресу, стрічок, відповідей LLM;
- WebSocket - двобічний канал. Потрібен, коли й клієнт часто надсилає дані: чат, спільне редагування, присутність користувачів онлайн.
Laravel Reverb - власний WebSocket-сервер Laravel, сумісний із протоколом Pusher. Застосунок транслює події, а клієнти підписуються через Laravel Echo:
php artisan install:broadcasting --reverb
php artisan reverb:start
class OrderShipped implements ShouldBroadcast
{
public function __construct(public Order $order) {}
public function broadcastOn(): array
{
return [new PrivateChannel("orders.{$this->order->user_id}")];
}
}
Echo.private(`orders.${userId}`).listen('OrderShipped', (event) => {
updateStatus(event.order);
});
Приватні канали авторизуються в routes/channels.php - Reverb пускає лише тих, кому дозволено слухати канал.
Що враховувати в продакшені:
- Reverb - окремий довгоживучий процес (Supervisor, контейнер), за зворотним проксі з підтримкою WebSocket;
- ліміт відкритих файлів ОС - кожне з'єднання займає дескриптор;
- горизонтальне масштабування - кілька серверів Reverb обмінюються повідомленнями через Redis (
REVERB_SCALING_ENABLED); - події через чергу: трансляція з
ShouldBroadcastіде через чергу, тож без воркера повідомлення не надходять.
SSE в Laravel - response()->eventStream() у звичайному маршруті: без окремого сервера, але кожен відкритий потік тримає процес PHP.
API-шлюз - окремий сервіс-«вхідні двері» перед вашими API. Клієнти звертаються лише до нього, а він маршрутизує запити до внутрішніх сервісів.
клієнти → API-шлюз → сервіс замовлень
→ сервіс користувачів
→ сервіс платежів
Що зазвичай робить шлюз:
- маршрутизація:
/api/orders/*- до одного сервісу,/api/users/*- до іншого; - автентифікація: перевірка токенів чи API-ключів один раз на вході, далі - довірений заголовок з ідентифікатором користувача;
- обмеження частоти і квоти для клієнтів і тарифів;
- TLS-термінація, CORS, стиснення;
- кешування відповідей;
- трансформація: перетворення форматів, об'єднання відповідей кількох сервісів в одну;
- спостережуваність: централізовані журнали, метрики, трасування запитів;
- версіонування і поступове перемикання трафіку між версіями сервісу.
Приклади: Kong, Tyk, AWS API Gateway, Azure API Management, Cloudflare (частково), Traefik і Nginx як простіші варіанти.
Коли шлюз доречний:
- кілька сервісів, які мають виглядати як одне API;
- публічне API з ключами, тарифами, квотами й порталом для розробників;
- потрібно централізовано застосовувати політики, а не дублювати їх у кожному сервісі.
Коли він зайвий: один моноліт на Laravel. Маршрутизація, автентифікація (Sanctum, Passport), обмеження частоти (throttle) уже є у фреймворку, а зовнішній шлюз лише додасть ще одну ланку, яка може впасти.
Ризики шлюзу:
- єдина точка відмови і вузьке місце - потребує резервування;
- «розумний шлюз»: бізнес-логіка, що поступово переїжджає в конфігурацію шлюзу, стає некерованою. Шлюз має займатися наскрізними задачами, а не правилами предметної області;
- додаткова затримка на кожен запит.
Споріднений патерн - BFF (backend for frontend): окремий шлюз під кожен тип клієнта, що збирає дані саме під його екрани.
Питання з реальних технічних співбесід - 100 питань у 7 темах, розібраних із відповідями. Нижче - розбивка за рівнями та темами, якщо хочете звузити підготовку.
Готуєтесь до співбесіди не просто так: зараз на сайті 146 відкритих вакансій Laravel і PHP. Переглянути вакансії