ออกแบบ Webhooks อย่างไรให้เชื่อถือได้

Ashley Innocent

Ashley Innocent

13 March 2026

ออกแบบ Webhooks อย่างไรให้เชื่อถือได้

enterprise.banner.title

enterprise.banner.feature1

enterprise.banner.feature2

enterprise.banner.feature3

enterprise.banner.ctaB

สรุป

ออกแบบ Webhook ที่เชื่อถือได้ด้วยการลองใหม่แบบ exponential backoff (5-10 ครั้ง), คีย์ idempotency, การยืนยันลายเซ็น HMAC และการหมดเวลา 5 วินาที ส่งคืน 2xx ทันที ประมวลผลแบบอะซิงโครนัส Modern PetstoreAPI นำ Webhook มาใช้สำหรับการอัปเดตคำสั่งซื้อ การรับเลี้ยงสัตว์เลี้ยง และการแจ้งเตือนการชำระเงิน พร้อมการลองใหม่และความปลอดภัยเต็มรูปแบบ

บทนำ

คุณส่ง Webhook เพื่อแจ้งให้ลูกค้าทราบว่าสัตว์เลี้ยงของพวกเขาถูกรับเลี้ยงไปแล้ว แต่เซิร์ฟเวอร์ของลูกค้าขัดข้อง Webhook ของคุณล้มเหลว คุณควรลองใหม่หรือไม่? กี่ครั้ง? จะเกิดอะไรขึ้นหากลูกค้าได้รับ Webhook สองครั้งและเรียกเก็บเงินลูกค้าสองครั้ง?

Webhook คือการเรียกกลับ HTTP ที่ผลักดันเหตุการณ์ไปยัง URL ของลูกค้า สิ่งเหล่านี้ดูง่ายในทางทฤษฎี แต่ซับซ้อนในทางปฏิบัติ เครือข่ายล้มเหลว เซิร์ฟเวอร์ขัดข้อง และลูกค้ามีข้อบกพร่อง Webhook ที่ใช้งานจริงจำเป็นต้องมีตรรกะการลองใหม่ (retry logic), idempotency, ความปลอดภัย และการตรวจสอบ

Modern PetstoreAPI นำ Webhook ที่พร้อมสำหรับการใช้งานจริงมาใช้สำหรับการอัปเดตคำสั่งซื้อ การรับเลี้ยงสัตว์เลี้ยง และการแจ้งเตือนการชำระเงิน Webhook ทุกตัวมีตรรกะการลองใหม่ การยืนยันลายเซ็น และ idempotency

💡
หากคุณกำลังสร้างหรือทดสอบ Webhook, Apidog จะช่วยคุณทดสอบการส่ง Webhook, ตรวจสอบลายเซ็น และจำลองสถานการณ์ความล้มเหลว คุณสามารถทดสอบตรรกะการลองใหม่และตรวจสอบการจัดการ idempotency ได้
ปุ่ม

ในคู่มือนี้ คุณจะได้เรียนรู้วิธีการออกแบบ Webhook ที่เชื่อถือได้โดยใช้รูปแบบของ Modern PetstoreAPI

พื้นฐาน Webhook

Webhook คือคำขอ HTTP POST ที่ส่งไปยัง URL ที่ลูกค้าให้ไว้เมื่อเกิดเหตุการณ์ขึ้น

Webhook ทำงานอย่างไร

1. ลูกค้าลงทะเบียน URL ของ Webhook:

POST /webhooks
{
  "url": "https://client.com/webhooks/petstore",
  "events": ["pet.adopted", "order.completed"]
}

2. เกิดเหตุการณ์ขึ้น (สัตว์เลี้ยงถูกรับเลี้ยง)

3. เซิร์ฟเวอร์ส่ง Webhook:

POST https://client.com/webhooks/petstore
Content-Type: application/json
X-Webhook-Signature: sha256=abc123...

{
  "event": "pet.adopted",
  "data": {
    "petId": "019b4132",
    "userId": "user-456",
    "timestamp": "2026-03-13T10:30:00Z"
  }
}

4. ลูกค้าตอบกลับ:

200 OK

ปัญหาความน่าเชื่อถือ

Webhook อาจล้มเหลวได้หลายสาเหตุ:

หากไม่มีตรรกะการลองใหม่ เหตุการณ์จะสูญหายไป หากไม่มี idempotency การส่ง Webhook ซ้ำกันจะทำให้เกิดการดำเนินการซ้ำซ้อน

ตรรกะการลองใหม่ด้วย Exponential Backoff

ลองส่ง Webhook ที่ล้มเหลวซ้ำอีกครั้งด้วยการหน่วงเวลาที่เพิ่มขึ้น

กลยุทธ์ Exponential Backoff

Attempt 1: ทันที
Attempt 2: 1 วินาทีต่อมา
Attempt 3: 2 วินาทีต่อมา
Attempt 4: 4 วินาทีต่อมา
Attempt 5: 8 วินาทีต่อมา
Attempt 6: 16 วินาทีต่อมา

ทำไมต้องเป็นแบบ exponential? หากลูกค้าออฟไลน์ การลองใหม่ซ้ำๆ จะไม่ช่วยอะไร Exponential backoff ให้เวลาในการกู้คืนระบบ

การนำไปใช้งาน

async function sendWebhook(url, payload, attempt = 1, maxAttempts = 6) {
  try {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Webhook-Signature': generateSignature(payload)
      },
      body: JSON.stringify(payload),
      timeout: 5000 // 5 second timeout
    });

    if (response.ok) {
      return { success: true, attempt };
    }

    // Retry on 5xx errors
    if (response.status >= 500 && attempt < maxAttempts) {
      const delay = Math.pow(2, attempt - 1) * 1000;
      await sleep(delay);
      return sendWebhook(url, payload, attempt + 1, maxAttempts);
    }

    // Don't retry 4xx errors (client error)
    return { success: false, status: response.status };

  } catch (error) {
    // Network error or timeout - retry
    if (attempt < maxAttempts) {
      const delay = Math.pow(2, attempt - 1) * 1000;
      await sleep(delay);
      return sendWebhook(url, payload, attempt + 1, maxAttempts);
    }
    return { success: false, error: error.message };
  }
}

ควรลองใหม่เมื่อใด

ลองใหม่เมื่อ:

ไม่ต้องลองใหม่เมื่อ:

Dead Letter Queue

หลังจากลองใหม่ครบจำนวนสูงสุดแล้ว ให้ย้าย Webhook ที่ล้มเหลวไปยัง Dead Letter Queue เพื่อตรวจสอบด้วยตนเอง:

if (!result.success) {
  await deadLetterQueue.add({
    url,
    payload,
    attempts: maxAttempts,
    lastError: result.error,
    timestamp: new Date()
  });
}

Idempotency เพื่อป้องกันการทำซ้ำ

ลูกค้าอาจได้รับ Webhook เดียวกันหลายครั้ง Idempotency ช่วยป้องกันการประมวลผลซ้ำซ้อน

คีย์ Idempotency

รวม ID ที่ไม่ซ้ำกันกับ Webhook แต่ละตัว:

{
  "id": "webhook_019b4132",
  "event": "pet.adopted",
  "data": {...}
}

ลูกค้าจัดเก็บ ID ที่ประมวลผลแล้ว:

app.post('/webhooks/petstore', async (req, res) => {
  const webhookId = req.body.id;

  // Check if already processed
  const processed = await db.webhooks.findOne({ id: webhookId });
  if (processed) {
    return res.status(200).json({ message: 'Already processed' });
  }

  // Process webhook
  await processPetAdoption(req.body.data);

  // Mark as processed
  await db.webhooks.insert({ id: webhookId, processedAt: new Date() });

  res.status(200).json({ message: 'Processed' });
});

การดำเนินการแบบ Idempotent

ออกแบบการดำเนินการให้เป็นแบบ idempotent:

ไม่ดี (ไม่เป็น idempotent):

// Charging twice causes double charge
await chargeCustomer(userId, amount);

ดี (เป็น idempotent):

// Charging with idempotency key prevents double charge
await chargeCustomer(userId, amount, { idempotencyKey: webhookId });

การยืนยันลายเซ็นเพื่อความปลอดภัย

ตรวจสอบว่า Webhook มาจาก API ของคุณ ไม่ใช่จากผู้โจมตี

ลายเซ็น HMAC

สร้างลายเซ็นโดยใช้ shared secret:

// Server generates signature
const crypto = require('crypto');

function generateSignature(payload, secret) {
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(JSON.stringify(payload));
  return hmac.digest('hex');
}

// Include in header
headers['X-Webhook-Signature'] = `sha256=${generateSignature(payload, webhookSecret)}`;

ไคลเอ็นต์ยืนยันลายเซ็น:

function verifySignature(payload, signature, secret) {
  const expected = generateSignature(payload, secret);
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(`sha256=${expected}`)
  );
}

app.post('/webhooks/petstore', (req, res) => {
  const signature = req.headers['x-webhook-signature'];

  if (!verifySignature(req.body, signature, WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Process webhook
  ...
});

การตรวจสอบเวลา

รวม timestamp เพื่อป้องกันการโจมตีแบบ replay:

{
  "id": "webhook_019b4132",
  "timestamp": "2026-03-13T10:30:00Z",
  "event": "pet.adopted",
  "data": {...}
}

ปฏิเสธ Webhook เก่า:

const webhookAge = Date.now() - new Date(req.body.timestamp);
if (webhookAge > 5 * 60 * 1000) { // 5 minutes
  return res.status(400).json({ error: 'Webhook too old' });
}

การจัดการการหมดเวลา

กำหนด aggressive timeouts เพื่อป้องกันไม่ให้ไคลเอ็นต์ที่ทำงานช้าทำให้ระบบของคุณหยุดชะงัก

หมดเวลา 5 วินาที

const response = await fetch(url, {
  method: 'POST',
  body: JSON.stringify(payload),
  timeout: 5000 // 5 seconds
});

ทำไมต้อง 5 วินาที? Webhook ควรส่งคืนทันที หากไคลเอ็นต์ใช้เวลานานกว่านั้น แสดงว่าพวกเขากำลังประมวลผลแบบ synchronous (รูปแบบที่ไม่ถูกต้อง)

รูปแบบการประมวลผลแบบ Asynchronous

ไม่ดี (synchronous):

app.post('/webhooks/petstore', async (req, res) => {
  // This takes 30 seconds - webhook will timeout
  await processOrder(req.body.data);
  await sendEmail(req.body.data);
  await updateInventory(req.body.data);

  res.status(200).json({ message: 'Processed' });
});

ดี (asynchronous):

app.post('/webhooks/petstore', async (req, res) => {
  // Return immediately
  res.status(200).json({ message: 'Received' });

  // Process asynchronously
  queue.add('process-webhook', req.body);
});

Modern PetstoreAPI นำ Webhook ไปใช้งานอย่างไร

Modern PetstoreAPI นำ Webhook ที่พร้อมสำหรับการใช้งานจริงมาใช้

เหตุการณ์ Webhook

pet.adopted - สัตว์เลี้ยงถูกรับเลี้ยง
pet.status_changed - สถานะสัตว์เลี้ยงเปลี่ยนไป
order.created - สร้างคำสั่งซื้อ
order.completed - คำสั่งซื้อเสร็จสมบูรณ์
payment.succeeded - การชำระเงินสำเร็จ
payment.failed - การชำระเงินล้มเหลว

เพย์โหลด Webhook

{
  "id": "webhook_019b4132-70aa-764f-b315-e2803d882a24",
  "event": "pet.adopted",
  "timestamp": "2026-03-13T10:30:00Z",
  "data": {
    "petId": "019b4132-70aa-764f-b315-e2803d882a24",
    "userId": "user-456",
    "orderId": "order-789",
    "adoptionDate": "2026-03-13"
  },
  "apiVersion": "v1"
}

การกำหนดค่าการลองใหม่

ความปลอดภัย

การทดสอบ Webhook ด้วย Apidog

Apidog รองรับการทดสอบ Webhook

ทดสอบการส่ง Webhook

  1. สร้าง mock webhook endpoint ใน Apidog
  2. ลงทะเบียน endpoint กับ PetstoreAPI
  3. ทริกเกอร์เหตุการณ์ (รับเลี้ยงสัตว์เลี้ยง)
  4. ตรวจสอบว่าได้รับ Webhook
  5. ตรวจสอบรูปแบบเพย์โหลด

ทดสอบการยืนยันลายเซ็น

// Apidog test script
const signature = pm.request.headers.get('X-Webhook-Signature');
const payload = pm.request.body.raw;
const secret = pm.environment.get('WEBHOOK_SECRET');

const expected = generateSignature(payload, secret);
pm.test('Signature valid', () => {
  pm.expect(signature).to.equal(`sha256=${expected}`);
});

ทดสอบตรรกะการลองใหม่

  1. ส่งคืนข้อผิดพลาด 500 จาก mock endpoint
  2. ตรวจสอบการลองใหม่ด้วย exponential backoff
  3. ตรวจสอบ dead letter queue หลังจากลองใหม่ครบจำนวนสูงสุด

ทดสอบ Idempotency

  1. รับ Webhook
  2. ส่งคืน 200
  3. รับ Webhook เดิมอีกครั้ง (จำลองการลองใหม่)
  4. ตรวจสอบว่าไม่มีการประมวลผลซ้ำซ้อน

บทสรุป

Webhook ที่เชื่อถือได้ต้องการ:

Modern PetstoreAPI นำรูปแบบทั้งหมดเหล่านี้ไปใช้ ตรวจสอบ เอกสารประกอบ Webhook สำหรับตัวอย่างที่สมบูรณ์

ทดสอบ Webhook ของคุณด้วย Apidog เพื่อตรวจสอบตรรกะการลองใหม่ ลายเซ็น และ idempotency ก่อนนำไปใช้งานจริง

ปุ่ม

คำถามที่พบบ่อย

Webhook ควรมียอดการลองใหม่กี่ครั้ง?

5-10 ครั้งด้วย exponential backoff สิ่งนี้ครอบคลุมการหยุดทำงานชั่วคราว (5-17 นาที) โดยไม่ทำให้ลูกค้ามีภาระมากเกินไป

Webhook ควรถอนการลองใหม่เมื่อเกิดข้อผิดพลาด 4xx หรือไม่?

ไม่ ข้อผิดพลาด 4xx บ่งชี้ปัญหาของลูกค้า (URL ไม่ถูกต้อง, การยืนยันตัวตนล้มเหลว) การลองใหม่จะไม่แก้ไขสิ่งเหล่านี้ ลองใหม่เฉพาะข้อผิดพลาด 5xx และความล้มเหลวของเครือข่ายเท่านั้น

Webhook ควรหมดเวลาภายในระยะเวลาเท่าใด?

สูงสุด 5 วินาที ไคลเอ็นต์ควรส่งคืน 200 ทันทีและประมวลผลแบบอะซิงโครนัส การหมดเวลาที่นานกว่านั้นบ่งชี้ว่าไคลเอ็นต์กำลังประมวลผลแบบ synchronous

จะเกิดอะไรขึ้นหากไคลเอ็นต์ไม่ตอบสนองต่อ Webhook เลย?

หลังจากลองใหม่ครบจำนวนสูงสุด ให้ย้ายไปยัง dead letter queue แจ้งเตือนลูกค้าทางอีเมล พิจารณาปิดใช้งาน Webhook สำหรับลูกค้ารายนั้นหลังจากความล้มเหลวซ้ำๆ

URL ของ Webhook ควรเป็น HTTPS หรือไม่?

ใช่ ต้องใช้ HTTPS เสมอ Webhook แบบ HTTP อาจถูกดักจับและแก้ไขได้ Modern PetstoreAPI ปฏิเสธ URL ของ Webhook แบบ HTTP

คุณจะป้องกันการโจมตีแบบ Replay ได้อย่างไร?

รวม timestamp ในเพย์โหลดและปฏิเสธ Webhook ที่มีอายุมากกว่า 5 นาที ร่วมกับการยืนยันลายเซ็น

ไคลเอ็นต์สามารถขอให้ส่ง Webhook ซ้ำได้หรือไม่?

ใช่ Modern PetstoreAPI มี endpoint สำหรับการส่ง Webhook ที่เฉพาะเจาะจงซ้ำ: POST /webhooks/{id}/redeliver

คุณทดสอบ Webhook ในเครื่องได้อย่างไร?

ใช้เครื่องมืออย่าง ngrok เพื่อเปิดเผย localhost สู่สาธารณะ หรือใช้ mock server ของ Apidog เพื่อจำลอง Webhook endpoint ระหว่างการพัฒนา

ฝึกการออกแบบ API แบบ Design-first ใน Apidog

ค้นพบวิธีที่ง่ายขึ้นในการสร้างและใช้ API

ออกแบบ Webhooks อย่างไรให้เชื่อถือได้