Налаштування Webhook-системи з підписом повідомлень (HMAC)

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Налаштування Webhook-системи з підписом повідомлень (HMAC)
Середня
від 1 робочого дня до 3 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Налаштування Webhook-системи з підписом повідомлень (HMAC)

HMAC (Hash-based Message Authentication Code) підпис у webhook гарантує, що повідомлення дійсно прийшло від очікуваного відправника та не було змінено в транзиті. Без перевірки підпису будь-який зловмисник може відправити підроблений webhook на ваш endpoint.

Принцип роботи HMAC

  1. Відправник і отримувач домовляються про секретний ключ
  2. При відправці: відправник обчислює HMAC-SHA256(payload, secret) і додає в заголовок
  3. При отриманні: отримувач обчислює те саме значення та порівнює з заголовком
  4. Якщо збігаються — повідомлення автентичне

Генерація підпису при відправці webhook

import hmac
import hashlib
import json
import requests

def send_webhook(url: str, payload: dict, secret: str):
    body = json.dumps(payload, separators=(',', ':'))
    timestamp = int(time.time())

    # Підпис включає timestamp для захисту від replay атак
    message = f"{timestamp}.{body}"
    signature = hmac.new(
        secret.encode(),
        message.encode(),
        hashlib.sha256
    ).hexdigest()

    response = requests.post(
        url,
        data=body,
        headers={
            'Content-Type': 'application/json',
            'X-Webhook-Timestamp': str(timestamp),
            'X-Webhook-Signature': f"sha256={signature}",
            'X-Webhook-ID': str(uuid.uuid4()),
        },
        timeout=10
    )

    return response

Верифікація підпису на стороні отримувача

import hmac
import hashlib
import time

def verify_webhook_signature(request) -> bool:
    secret = os.environ['WEBHOOK_SECRET']

    # Витягти з заголовків
    timestamp = request.headers.get('X-Webhook-Timestamp')
    received_sig = request.headers.get('X-Webhook-Signature', '')

    if not timestamp or not received_sig:
        return False

    # Захист від replay атаки: не приймати події старші за 5 хвилин
    if abs(time.time() - int(timestamp)) > 300:
        return False

    # Обчислити очікуваний підпис
    body = request.get_data()  # raw bytes, перед парсингом!
    message = f"{timestamp}.{body.decode()}".encode()
    expected_sig = "sha256=" + hmac.new(
        secret.encode(),
        message,
        hashlib.sha256
    ).hexdigest()

    # Constant-time порівняння для захисту від timing атак
    return hmac.compare_digest(expected_sig, received_sig)


@app.route('/webhooks/payments', methods=['POST'])
def payment_webhook():
    if not verify_webhook_signature(request):
        return jsonify({'error': 'Invalid signature'}), 401

    # Безпечно обробити payload
    event = request.get_json()
    process_payment_event(event)

    return jsonify({'status': 'ok'})

Stripe-сумісний формат

Stripe використовує t=timestamp,v1=signature у заголовку Stripe-Signature:

def verify_stripe_webhook(payload, sig_header, secret):
    # Stripe формат: "t=1614556800,v1=abcdef..."
    elements = dict(e.split('=') for e in sig_header.split(','))
    timestamp = elements.get('t')
    sig = elements.get('v1')

    signed_payload = f"{timestamp}.{payload}"
    expected = hmac.new(secret.encode(), signed_payload.encode(), hashlib.sha256).hexdigest()

    return hmac.compare_digest(expected, sig)

Логіка повтору та Idempotency

class WebhookDelivery:
    MAX_ATTEMPTS = 5
    RETRY_DELAYS = [10, 30, 120, 600, 3600]  # секунди між спробами

    def deliver_with_retry(self, webhook_id: str, url: str, payload: dict, secret: str):
        for attempt, delay in enumerate(self.RETRY_DELAYS):
            try:
                response = send_webhook(url, payload, secret)

                if response.status_code < 300:
                    db.mark_delivered(webhook_id)
                    return True

                db.log_attempt(webhook_id, attempt + 1, response.status_code)

            except requests.exceptions.Timeout:
                db.log_attempt(webhook_id, attempt + 1, error='timeout')

            if attempt < len(self.RETRY_DELAYS) - 1:
                time.sleep(delay)

        db.mark_failed(webhook_id)
        return False

Idempotency на стороні отримувача

def handle_webhook_idempotent(webhook_id: str, handler_fn):
    """Запобігти подвійній обробці при повторі"""
    if db.is_processed(webhook_id):
        return  # Вже оброблено

    with db.transaction():
        db.mark_processing(webhook_id)
        handler_fn()
        db.mark_processed(webhook_id)