DevOps

Webhook Design Pattern Docker Container Deploy

Webhook Design Pattern Docker Container Deploy | SiamCafe Blog

โดย อ. บอมกิตติทัศน์เจริญพนาสิทธิ์ | อัปเดต 24 ก. พ. 2026 | อ่าน 15 นาที

Webhook คืออะไร — Push vs Pull Model

Webhook คือกลไกที่ให้ระบบหนึ่ง (Producer) ส่ง HTTP POST Request ไปยัง URL ที่กำหนดไว้ล่วงหน้า (Consumer) เมื่อเกิด Event บางอย่างเป็นรูปแบบ Push Notification แบบ Server-to-Server ที่ไม่ต้อง Polling ทำให้ได้รับข้อมูลแบบ Near Real-time และประหยัดทรัพยากร

ความแตกต่างระหว่าง Pull Model (API Polling) กับ Push Model (Webhook) คือใน Pull Model ฝั่ง Client ต้องส่ง Request ไปถาม Server ซ้ำๆเป็นระยะว่ามีข้อมูลใหม่หรือยังถ้าถามบ่อยเกินไปเปลือง Bandwidth ถ้าถามห่างเกินไปได้ข้อมูลช้าส่วนใน Push Model (Webhook) Server จะส่งข้อมูลมาหาเราทันทีเมื่อเกิด Event ไม่ต้องถามซ้ำลดทั้ง Latency และ Bandwidth อย่างมาก

ตัวอย่าง Webhook ที่ใช้กันแพร่หลายได้แก่ GitHub Webhook แจ้งเมื่อมี Push/PR, Stripe Webhook แจ้งเมื่อมี Payment, LINE Messaging API แจ้งเมื่อมีข้อความใหม่, Slack Webhook ส่งข้อความเข้า Channel และ Discord Webhook สำหรับ Bot Notification

Webhook ทำงานอย่างไร — Flow ทั้งระบบ

  1. Registration — Consumer ลงทะเบียน URL ปลายทาง (Endpoint) กับ Producer พร้อม Secret Key สำหรับ Verify Signature
  2. Event เกิดขึ้น — เช่น User ชำระเงินสำเร็จบน Stripe
  3. Producer สร้าง Payload — รวบรวมข้อมูล Event เป็น JSON พร้อมสร้าง Signature ด้วย HMAC-SHA256
  4. HTTP POST — Producer ส่ง POST Request พร้อม Payload และ Signature Header ไปยัง Consumer URL
  5. Consumer รับและ Verify — ตรวจ Signature ว่าถูกต้องตอบ 200 OK ทันที
  6. Async Processing — Consumer ส่ง Payload เข้า Queue แล้ว Worker Process ทีหลัง
  7. Retry (ถ้าล้มเหลว) — ถ้า Consumer ไม่ตอบ 2xx Producer จะ Retry ตาม Schedule (เช่น 1, 5, 30, 60 นาที)

Pattern 1: Direct Processing (ไม่แนะนำสำหรับ Production)

Webhook Receiver รับ Request แล้ว Process ทันทีก่อนตอบ 200 ปัญหาคือถ้า Processing ช้า (เช่นต้อง Query Database, เรียก API อื่น) อาจ Timeout ทำให้ Producer คิดว่า Delivery ล้มเหลวแล้ว Retry ทำให้เกิด Duplicate Processing

Pattern 2: Queue-based Processing (แนะนำ)

Webhook Receiver รับ Request → Validate Signature → ส่ง Payload เข้า Message Queue → ตอบ 200 OK ทันทีส่วน Worker Process อ่านจาก Queue แล้ว Process แยกต่างหากข้อดีคือ Receiver ตอบเร็วมากไม่มี Timeout, Decouple Receiving กับ Processing, Scale Worker แยกจาก Receiver ได้และไม่หาย Payload แม้ Worker ล่ม

Pattern 3: Fan-out Processing

Webhook เดียวอาจต้อง Trigger หลาย Action เช่นเมื่อ Payment สำเร็จต้องอัปเดต Order, ส่ง Email, อัปเดต Inventory พร้อมกันใช้ Fan-out Pattern กระจาย Event ไปหลาย Queue/Topic แต่ละ Consumer จัดการ Action ของตัวเอง

Webhook Receiver Architecture — Queue-based Processing

สถาปัตยกรรมที่แนะนำสำหรับ Production ประกอบด้วย 4 Component หลัก

Webhook Security — Signature, HMAC, Rate Limit

ความปลอดภัยเป็นสิ่งสำคัญมากสำหรับ Webhook เพราะ Endpoint เป็น Public URL ที่ใครก็ส่ง Request มาได้ต้องมีกลไกยืนยันว่า Request มาจากแหล่งที่ถูกต้องจริง

HMAC Signature Verification

วิธีที่นิยมที่สุดคือ Producer สร้าง HMAC-SHA256 จาก Payload + Secret Key แล้วแนบเป็น Header มาใน Request ฝั่ง Consumer คำนวณ HMAC จาก Payload ที่ได้รับ + Secret Key เดียวกันแล้วเปรียบเทียบว่าตรงกันหรือไม่

// Node.js — Verify Webhook Signature
const crypto = require('crypto');

function verifySignature(payload, signature, secret) {
 const expected = crypto
 .createHmac('sha256', secret)
 .update(payload, 'utf8')
 .digest('hex');
 
 // ใช้ timingSafeEqual ป้องกัน Timing Attack
 return crypto.timingSafeEqual(
 Buffer.from(signature),
 Buffer.from(`sha256=`)
 );
}

มาตรการ Security เพิ่มเติม

Retry Strategy และ Idempotency

Webhook Delivery อาจล้มเหลวได้หลายสาเหตุเช่น Network Error, Server Down, Timeout ดังนั้น Producer จะมีระบบ Retry ฝั่ง Consumer ต้องออกแบบให้รองรับ Duplicate Delivery ได้

Retry Strategy ทั่วไป ใช้ Exponential Backoff เช่น Retry ครั้งที่ 1 หลัง 1 นาที, ครั้งที่ 2 หลัง 5 นาที, ครั้งที่ 3 หลัง 30 นาที, ครั้งที่ 4 หลัง 2 ชั่วโมง, ครั้งที่ 5 หลัง 24 ชั่วโมงหลังจากนั้น Mark เป็น Failed

Idempotency คือการทำให้ Process Webhook ซ้ำกี่ครั้งก็ได้ผลลัพธ์เหมือนกันวิธีทำคือใช้ Event ID ที่ Provider แนบมาในทุก Webhook เป็น Deduplication Key เก็บ Event ID ที่ Process แล้วไว้ใน Database หรือ Redis ถ้าเจอ Event ID ซ้ำก็ Skip ไม่ Process อีก

// Idempotency Check
async function processWebhook(eventId, payload) {
 // ตรวจว่าเคย Process แล้วหรือยัง
 const exists = await redis.get(`webhook:`);
 if (exists) {
 console.log(`Event  already processed, skipping`);
 return;
 }
 
 // Process Business Logic
 await handlePaymentSuccess(payload);
 
 // Mark ว่า Process แล้ว (TTL 7 วัน)
 await redis.set(`webhook:`, '1', 'EX', 604800);
}

เขียน Webhook Receiver ด้วย Node.js

// webhook-receiver.js
const express = require('express');
const crypto = require('crypto');
const { Queue } = require('bullmq');
const Redis = require('ioredis');

const app = express();
const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');
const webhookQueue = new Queue('webhooks', { connection: redis });

// ใช้ raw body สำหรับ Signature Verification
app.use('/webhook', express.raw({ type: 'application/json' }));

app.post('/webhook/:source', async (req, res) => {
 try {
 const source = req.params.source;
 const signature = req.headers['x-webhook-signature'];
 const payload = req.body;
 
 // 1. Verify Signature
 const secret = process.env[`WEBHOOK_SECRET_`];
 if (!verifySignature(payload, signature, secret)) {
 return res.status(401).json({ error: 'Invalid signature' });
 }
 
 // 2. Parse Payload
 const data = JSON.parse(payload);
 
 // 3. Idempotency Check
 const eventId = data.id || req.headers['x-event-id'];
 const exists = await redis.get(`wh:`);
 if (exists) {
 return res.status(200).json({ status: 'already_processed' });
 }
 
 // 4. Push to Queue (ไม่ Process ที่นี่!)
 await webhookQueue.add(source, {
 eventId,
 source,
 payload: data,
 receivedAt: new Date().toISOString()
 }, {
 attempts: 3,
 backoff: { type: 'exponential', delay: 5000 }
 });
 
 // 5. Mark received
 await redis.set(`wh:`, 'queued', 'EX', 604800);
 
 // 6. ตอบ 200 ทันที!
 res.status(200).json({ status: 'accepted' });
 
 } catch (err) {
 console.error('Webhook error:', err);
 res.status(500).json({ error: 'Internal error' });
 }
});

function verifySignature(payload, signature, secret) {
 if (!signature || !secret) return false;
 const expected = 'sha256=' + crypto
 .createHmac('sha256', secret)
 .update(payload)
 .digest('hex');
 try {
 return crypto.timingSafeEqual(
 Buffer.from(signature),
 Buffer.from(expected)
 );
 } catch { return false; }
}

app.listen(3000, () => console.log('Webhook receiver on :3000'));

เขียน Webhook Sender ด้วย Python

# webhook_sender.py
import hmac
import hashlib
import json
import time
import requests
from datetime import datetime

class WebhookSender:
 def __init__(self, target_url: str, secret: str):
 self.target_url = target_url
 self.secret = secret
 self.max_retries = 5
 self.retry_delays = [60, 300, 1800, 7200, 86400]
 
 def sign(self, payload: bytes) -> str:
 return 'sha256=' + hmac.new(
 self.secret.encode(),
 payload,
 hashlib.sha256
 ).hexdigest()
 
 def send(self, event_type: str, data: dict) -> bool:
 payload = json.dumps({
 'id': f'evt_{int(time.time()*1000)}',
 'type': event_type,
 'data': data,
 'created_at': datetime.utcnow().isoformat()
 }).encode()
 
 signature = self.sign(payload)
 headers = {
 'Content-Type': 'application/json',
 'X-Webhook-Signature': signature,
 'X-Event-ID': json.loads(payload)['id']
 }
 
 for attempt in range(self.max_retries):
 try:
 resp = requests.post(
 self.target_url,
 data=payload,
 headers=headers,
 timeout=30
 )
 if resp.status_code == 200:
 return True
 print(f'Attempt {attempt+1} failed: {resp.status_code}')
 except requests.RequestException as e:
 print(f'Attempt {attempt+1} error: {e}')
 
 if attempt < self.max_retries - 1:
 time.sleep(self.retry_delays[attempt])
 
 return False

# ใช้งาน
sender = WebhookSender(
 target_url='https://example.com/webhook/payment',
 secret='your-secret-key'
)
sender.send('payment.success', {
 'order_id': 'ORD-001',
 'amount': 1500,
 'currency': 'THB'
})

Dockerize Webhook Application

# Dockerfile
FROM node:20-alpine AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .

# Health Check
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
 CMD wget -qO- http://localhost:3000/health || exit 1

# ใช้ non-root user
RUN addgroup -g 1001 appgroup && \
 adduser -u 1001 -G appgroup -s /bin/sh -D appuser
USER appuser

EXPOSE 3000
CMD ["node", "webhook-receiver.js"]

หลักการสำคัญของ Dockerfile สำหรับ Production ได้แก่ใช้ Alpine Image ลดขนาด, npm ci แทน npm install เพื่อ Reproducibility, มี Health Check สำหรับ Orchestrator, รัน Process เป็น Non-root User เพิ่มความปลอดภัยและใช้ Multi-stage Build ถ้ามีขั้นตอน Build

Docker Compose — จัดการ Multi-container

# docker-compose.yml
version: '3.8'

services:
 webhook-receiver:
 build: .
 ports:
 - "3000:3000"
 environment:
 - REDIS_URL=redis://redis:6379
 - WEBHOOK_SECRET_STRIPE=whsec_xxx
 - WEBHOOK_SECRET_GITHUB=ghsec_xxx
 depends_on:
 redis:
 condition: service_healthy
 deploy:
 replicas: 2
 resources:
 limits:
 cpus: '0.5'
 memory: 256M
 restart: unless-stopped

 webhook-worker:
 build: .
 command: ["node", "worker.js"]
 environment:
 - REDIS_URL=redis://redis:6379
 - DATABASE_URL=postgres://user:pass@postgres:5432/webhooks
 depends_on:
 redis:
 condition: service_healthy
 postgres:
 condition: service_healthy
 deploy:
 replicas: 3
 restart: unless-stopped

 redis:
 image: redis:7-alpine
 volumes:
 - redis_data:/data
 healthcheck:
 test: ["CMD", "redis-cli", "ping"]
 interval: 10s
 timeout: 5s
 retries: 5
 restart: unless-stopped

 postgres:
 image: postgres:16-alpine
 environment:
 - POSTGRES_DB=webhooks
 - POSTGRES_USER=user
 - POSTGRES_PASSWORD=pass
 volumes:
 - pg_data:/var/lib/postgresql/data
 healthcheck:
 test: ["CMD-SHELL", "pg_isready -U user -d webhooks"]
 interval: 10s
 timeout: 5s
 retries: 5
 restart: unless-stopped

volumes:
 redis_data:
 pg_data:

Docker Compose นี้สร้าง 4 Service ได้แก่ Webhook Receiver 2 Replica รับ HTTP Request, Worker 3 Replica Process จาก Queue, Redis เป็น Message Queue และ PostgreSQL เก็บ Log และข้อมูลธุรกิจทุก Service มี Health Check และ Restart Policy

Docker Swarm

# Init Swarm
docker swarm init

# Deploy Stack
docker stack deploy -c docker-compose.yml webhook-stack

# Scale Worker
docker service scale webhook-stack_webhook-worker=5

# ดู Status
docker service ls
docker service logs webhook-stack_webhook-receiver

Kubernetes Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
 name: webhook-receiver
spec:
 replicas: 3
 selector:
 matchLabels:
 app: webhook-receiver
 template:
 metadata:
 labels:
 app: webhook-receiver
 spec:
 containers:
 - name: receiver
 image: registry.example.com/webhook-receiver:v1.2.0
 ports:
 - containerPort: 3000
 env:
 - name: REDIS_URL
 valueFrom:
 secretKeyRef:
 name: webhook-secrets
 key: redis-url
 resources:
 requests:
 cpu: 100m
 memory: 128Mi
 limits:
 cpu: 500m
 memory: 256Mi
 livenessProbe:
 httpGet:
 path: /health
 port: 3000
 initialDelaySeconds: 10
 periodSeconds: 30
 readinessProbe:
 httpGet:
 path: /health
 port: 3000
 initialDelaySeconds: 5
 periodSeconds: 10

CI/CD Pipeline สำหรับ Webhook Service

# .github/workflows/deploy.yml
name: Deploy Webhook Service
on:
 push:
 branches: [main]

jobs:
 test:
 runs-on: ubuntu-latest
 steps:
 - uses: actions/checkout@v4
 - run: npm ci
 - run: npm test
 
 build-push:
 needs: test
 runs-on: ubuntu-latest
 steps:
 - uses: actions/checkout@v4
 - uses: docker/login-action@v3
 with:
 registry: ghcr.io
 username: }
 password: }
 - uses: docker/build-push-action@v5
 with:
 push: true
 tags: ghcr.io/}:}
 
 deploy:
 needs: build-push
 runs-on: ubuntu-latest
 steps:
 - name: Deploy to Kubernetes
 run: |
 kubectl set image deployment/webhook-receiver \
 receiver=ghcr.io/}:}

Monitoring และ Observability

Webhook Service ต้องมี Monitoring ที่ดีเพราะปัญหาอาจเกิดได้ทุกจุด Metrics ที่ต้องติดตามได้แก่

แนะนำใช้ Prometheus + Grafana สำหรับ Metrics และ ELK Stack (Elasticsearch + Logstash + Kibana) หรือ Loki สำหรับ Centralized Logging ตั้ง Alert เมื่อ Queue Depth เกิน Threshold หรือ Error Rate เกิน 1%

Best Practices รวม

สรุปและแหล่งเรียนรู้เพิ่มเติม

Webhook เป็น Design Pattern พื้นฐานที่สำคัญมากในสถาปัตยกรรมแบบ Event-driven การออกแบบ Webhook Receiver ที่ดีต้องคำนึงถึง Security (Signature Verification), Reliability (Queue-based Processing, Retry), Scalability (Containerized, Horizontally Scalable) และ Observability (Metrics, Logging, Alerting) การ Dockerize และ Deploy ด้วย Docker Compose หรือ Kubernetes ทำให้ Scale ได้ง่ายและ Reproduce Environment ได้ทุกที่

แหล่งเรียนรู้เพิ่มเติมได้แก่ Standard Webhooks (standardwebhooks.com) สำหรับ Best Practice มาตรฐาน, BullMQ Documentation สำหรับ Queue Library, Docker Official Documentation และ Kubernetes Documentation ติดตามบทความใหม่ๆได้ที่ SiamCafe.net

อ. บอมกิตติทัศน์เจริญพนาสิทธิ์
IT Infrastructure Expert | Thaiware Award | ประสบการณ์กว่า 25 ปีด้าน Network, Linux, Cloud & AI — ผู้ก่อตั้ง SiamCafe.net Since 2000-2026

Q: Webhook คืออะไร

กลไกที่ให้ระบบหนึ่งส่ง HTTP POST ไปยัง URL ที่กำหนดเมื่อเกิด Event เป็น Push Notification แบบ Server-to-Server ไม่ต้อง Polling ได้ข้อมูล Real-time

Q: Webhook ต่างจาก API อย่างไร

API เป็น Pull Model ต้องส่ง Request ไปถามเองส่วน Webhook เป็น Push Model Server ส่งข้อมูลมาเมื่อเกิด Event อัตโนมัติลด Latency และ Bandwidth

Q: Deploy Webhook Receiver ด้วย Docker ทำอย่างไร

สร้าง Dockerfile → Docker Compose จัดการ App + Queue + DB → docker compose up -d หรือ Push Image ขึ้น Registry แล้ว Deploy บน Kubernetes

Q: ทำอย่างไรให้ Webhook ไม่หายข้อมูล

ใช้ Message Queue เป็น Buffer ตอบ 200 OK ทันทีที่รับ Payload ได้มีระบบ Retry และ Dead Letter Queue สำหรับ Failed Messages

Q: Webhook Security ต้องทำอะไรบ้าง

Verify HMAC-SHA256 Signature ทุกครั้ง, HTTPS Only, Validate Payload Schema, Rate Limit, IP Whitelist และ Timestamp Check ป้องกัน Replay Attack

บทความแนะนำ:

อ่านเพิ่มเติม: บทความทั้งหมด | หน้าแรก Blog