APIs & Webhooks

Learn how to work with APIs and webhooks in your automation systems

การเชื่อมต่อระบบภายนอกด้วย APIs และ webhooks เพื่อสร้างระบบอัตโนมัติที่สมบูรณ์

Understanding APIs

What is an API?

API (Application Programming Interface) คือ:
├─ ช่องทางการสื่อสารระหว่างระบบ
├─ กำหนดรูปแบบการ request/response
├─ มี rules และ protocols ที่ชัดเจน
└─ ทำให้ระบบต่างๆ สามารถทำงานร่วมกันได้

Types of APIs

1. REST APIs (Most Common)
   ├─ HTTP methods: GET, POST, PUT, DELETE
   ├─ JSON/XML data format
   ├─ Stateless communication
   └─ Easy to use and understand

2. GraphQL APIs
   ├─ Single endpoint
   ├─ Query exactly what you need
   ├─ Strong typing
   └─ Good for complex data

3. SOAP APIs
   ├─ XML-based
   ├─ Strict standards
   ├─ Enterprise grade
   └─ More complex

4. Webhooks
   ├─ Reverse API
   ├─ Event-driven
   ├─ Real-time notifications
   └─ Server-to-server communication

Working with REST APIs

Making API Requests

// Basic GET request
async function fetchUserData(userId) {
  try {
    const response = await fetch(`https://api.example.com/users/${userId}`, {
      method: 'GET',
      headers: {
        'Authorization': 'Bearer your-api-key',
        'Content-Type': 'application/json'
      }
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    return data;
  } catch (error) {
    console.error('API request failed:', error);
    throw error;
  }
}

// POST request with data
async function createOrder(orderData) {
  try {
    const response = await fetch('https://api.example.com/orders', {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer your-api-key',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(orderData)
    });

    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Create order failed:', error);
    throw error;
  }
}

API Authentication Methods

// 1. API Key (Header)
const headers = {
  'X-API-Key': 'your-api-key-here',
  'Content-Type': 'application/json'
};

// 2. Bearer Token
const headers = {
  'Authorization': 'Bearer your-jwt-token',
  'Content-Type': 'application/json'
};

// 3. Basic Auth
const credentials = btoa('username:password');
const headers = {
  'Authorization': `Basic ${credentials}`,
  'Content-Type': 'application/json'
};

// 4. OAuth 2.0
async function getOAuthToken() {
  const response = await fetch('https://oauth.example.com/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      grant_type: 'client_credentials',
      client_id: 'your-client-id',
      client_secret: 'your-client-secret'
    })
  });
  
  const { access_token } = await response.json();
  return access_token;
}

Error Handling

async function robustAPICall(url, options = {}) {
  try {
    const response = await fetch(url, {
      timeout: 10000, // 10 second timeout
      ...options
    });

    // Handle different HTTP status codes
    switch (response.status) {
      case 200:
      case 201:
        return await response.json();
      
      case 400:
        throw new Error('Bad request - Invalid data');
      
      case 401:
        throw new Error('Unauthorized - Check API credentials');
      
      case 403:
        throw new Error('Forbidden - Insufficient permissions');
      
      case 404:
        throw new Error('Not found - Resource does not exist');
      
      case 429:
        throw new Error('Rate limit exceeded - Try again later');
      
      case 500:
        throw new Error('Server error - Try again later');
      
      default:
        throw new Error(`HTTP error! status: ${response.status}`);
    }
  } catch (error) {
    console.error('API call failed:', error);
    
    // Retry logic for transient errors
    if (error.message.includes('timeout') || error.message.includes('Server error')) {
      console.log('Retrying in 5 seconds...');
      await new Promise(resolve => setTimeout(resolve, 5000));
      return robustAPICall(url, options);
    }
    
    throw error;
  }
}

Webhooks Explained

What is a Webhook?

Webhook คือ:
├─ Reverse API pattern
├─ Server sends data to client
├─ Event-driven notifications
├─ Real-time data updates
└─ Reduces polling overhead

ตัวอย่างการใช้งาน:
├─ LINE Platform ส่ง messages มาที่เรา
├─ Stripe ส่ง payment notifications
├─ GitHub ส่ง commit notifications
└─ Supabase ส่ง database change notifications

Creating Webhook Endpoints

// n8n Function Node - LINE Webhook Handler
function handleLINEWebhook() {
  const events = $input.all()[0].json.events || [];
  const processedEvents = [];

  for (const event of events) {
    if (event.type === 'message' && event.message.type === 'text') {
      processedEvents.push({
        type: 'message',
        userId: event.source.userId,
        message: event.message.text,
        replyToken: event.replyToken,
        timestamp: event.timestamp
      });
    } else if (event.type === 'follow') {
      processedEvents.push({
        type: 'follow',
        userId: event.source.userId,
        timestamp: event.timestamp
      });
    } else if (event.type === 'unfollow') {
      processedEvents.push({
        type: 'unfollow',
        userId: event.source.userId,
        timestamp: event.timestamp
      });
    }
  }

  return processedEvents.map(event => ({ json: event }));
}

// Express.js Webhook Endpoint
app.post('/webhook/line', (req, res) => {
  const events = req.body.events || [];
  
  // Process each event
  events.forEach(async (event) => {
    if (event.type === 'message') {
      await processMessage(event);
    }
  });
  
  // Always return 200 OK
  res.status(200).json({ status: 'ok' });
});

Webhook Security

// Verify webhook signature (LINE example)
function verifyWebhookSignature(body, signature, channelSecret) {
  const crypto = require('crypto');
  
  const hash = crypto
    .createHmac('SHA256', channelSecret)
    .update(body, 'utf8')
    .digest('base64');
  
  return hash === signature;
}

// Express middleware for webhook verification
app.use('/webhook/line', (req, res, next) => {
  const signature = req.headers['x-line-signature'];
  const body = JSON.stringify(req.body);
  
  if (!verifyWebhookSignature(body, signature, process.env.LINE_CHANNEL_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  next();
});

Thai Service APIs

1. Thailand Post API

// Track Thailand Post parcels
async function trackThailandPost(trackingNumber) {
  const apiKey = process.env.THAILAND_POST_API_KEY;
  
  try {
    const response = await fetch(`https://trackapi.thailandpost.co.th/post/api/v1/track/${trackingNumber}`, {
      headers: {
        'Authorization': `Token ${apiKey}`,
        'Content-Type': 'application/json'
      }
    });

    const data = await response.json();
    
    if (data.status === 'success') {
      return data.response.items[0];
    } else {
      throw new Error('Tracking failed');
    }
  } catch (error) {
    console.error('Thailand Post API error:', error);
    throw error;
  }
}

2. DHL Thailand API

// DHL tracking for international shipments
async function trackDHL(waybillNumber) {
  try {
    const response = await fetch(`https://api-eu.dhl.com/track/shipments?trackingNumber=${waybillNumber}`, {
      headers: {
        'DHL-API-Key': process.env.DHL_API_KEY',
        'Content-Type': 'application/json'
      }
    });

    const data = await response.json();
    return data.shipments[0];
  } catch (error) {
    console.error('DHL API error:', error);
    throw error;
  }
}

3. Thai Government APIs

// DBD (Department of Business Development) API
async function searchBusinessRegistration(businessId) {
  try {
    const response = await fetch(`https://api.dbd.go.th/api/v1/business/${businessId}`, {
      headers: {
        'Authorization': `Bearer ${process.env.DBD_API_KEY}`,
        'Content-Type': 'application/json'
      }
    });

    const data = await response.json();
    return data;
  } catch (error) {
    console.error('DBD API error:', error);
    throw error;
  }
}

// Revenue Department API for tax verification
async function verifyTaxID(taxID) {
  try {
    const response = await fetch(`https://api.rd.go.th/api/v1/verify/${taxID}`, {
      headers: {
        'Authorization': `Bearer ${process.env.RD_API_KEY}`,
        'Content-Type': 'application/json'
      }
    });

    const data = await response.json();
    return data;
  } catch (error) {
    console.error('Revenue Department API error:', error);
    throw error;
  }
}

n8n API Integration

HTTP Request Node Configuration

{
  "method": "POST",
  "url": "https://api.example.com/endpoint",
  "authentication": "Header Auth",
  "headerAuth": {
    "name": "Authorization",
    "value": "Bearer {{ $credentials.apiKey }}"
  },
  "jsonParameters": true,
  "jsonBody": "{\n  \"customer_id\": \"{{ $json.customerId }}\",\n  \"items\": {{ $json.items }},\n  \"total\": {{ $json.total }}\n}",
  "options": {
    "timeout": 30000,
    "retry": {
      "enabled": true,
      "maxAttempts": 3
    }
  }
}

Function Node for API Processing

// Process API response in n8n
function processAPIResponse() {
  const response = $input.all()[0].json;
  
  // Extract relevant data
  const processedData = {
    orderId: response.order_id,
    status: response.status,
    customer: {
      id: response.customer.id,
      name: response.customer.name,
      email: response.customer.email
    },
    items: response.items.map(item => ({
      productId: item.product_id,
      name: item.name,
      quantity: item.quantity,
      price: item.price
    })),
    totalAmount: response.total_amount,
    createdAt: response.created_at
  };
  
  // Add processing timestamp
  processedData.processedAt = new Date().toISOString();
  
  return [{ json: processedData }];
}

Real-time Integration Examples

1. Order Processing Pipeline

Customer Order → API (Vercel) → Supabase → Webhook → n8n → LINE Notification
// Vercel API endpoint
export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }

  try {
    const { customerId, items, totalAmount } = req.body;
    
    // Save to Supabase
    const { data: order, error } = await supabase
      .from('orders')
      .insert({
        customer_id: customerId,
        items: items,
        total_amount: totalAmount,
        status: 'pending'
      })
      .select()
      .single();

    if (error) throw error;

    // Trigger n8n webhook
    await fetch('https://your-n8n-instance.com/webhook/new-order', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        orderId: order.order_id,
        customerId: customerId,
        totalAmount: totalAmount
      })
    });

    res.status(200).json({ success: true, order });

  } catch (error) {
    console.error('Order processing error:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
}

2. Real-time Chat System

LINE Message → n8n → Custom API → Supabase (Real-time) → Dashboard Update
// n8n Function Node
async function processChatMessage() {
  const message = $json.message;
  const userId = $json.userId;
  
  // Save to database
  const { data: savedMessage } = await supabase
    .from('messages')
    .insert({
      user_id: userId,
      message: message,
      timestamp: new Date().toISOString()
    })
    .select()
    .single();

  // Process with AI if needed
  const aiResponse = await processWithAI(message);
  
  // Update message with AI response
  await supabase
    .from('messages')
    .update({ ai_response: aiResponse })
    .eq('id', savedMessage.id);

  return [{
    json: {
      messageId: savedMessage.id,
      aiResponse: aiResponse,
      replyToken: $json.replyToken
    }
  }];
}

Best Practices

1. Rate Limiting

// Implement rate limiting
const rateLimiter = new Map();

async function rateLimitedAPICall(url, options, maxCalls = 100, windowMs = 60000) {
  const key = options.headers['Authorization'] || 'anonymous';
  const now = Date.now();
  const windowStart = now - windowMs;
  
  // Clean old entries
  for (const [k, calls] of rateLimiter.entries()) {
    rateLimiter.set(k, calls.filter(call => call > windowStart));
    if (rateLimiter.get(k).length === 0) {
      rateLimiter.delete(k);
    }
  }
  
  // Check current rate
  const currentCalls = rateLimiter.get(key) || [];
  if (currentCalls.length >= maxCalls) {
    throw new Error('Rate limit exceeded');
  }
  
  // Add current call
  currentCalls.push(now);
  rateLimiter.set(key, currentCalls);
  
  // Make API call
  return fetch(url, options);
}

2. Caching

// Simple cache implementation
const cache = new Map();

async function cachedAPICall(url, options, ttl = 300000) { // 5 minutes default
  const cacheKey = `${url}:${JSON.stringify(options)}`;
  const cached = cache.get(cacheKey);
  
  if (cached && (Date.now() - cached.timestamp) < ttl) {
    return cached.data;
  }
  
  const data = await fetch(url, options);
  cache.set(cacheKey, {
    data: await data.json(),
    timestamp: Date.now()
  });
  
  return cache.get(cacheKey).data;
}

3. Error Recovery

// Exponential backoff retry
async function retryAPICall(url, options, maxRetries = 3) {
  let lastError;
  
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);
      
      if (response.ok) {
        return response.json();
      }
      
      // Don't retry client errors
      if (response.status >= 400 && response.status < 500) {
        throw new Error(`Client error: ${response.status}`);
      }
      
    } catch (error) {
      lastError = error;
      
      if (attempt < maxRetries) {
        const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
        console.log(`Retry attempt ${attempt + 1} in ${delay}ms`);
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }
  
  throw lastError;
}

Monitoring & Debugging

API Call Logging

// Log all API calls for debugging
async function loggedAPICall(url, options) {
  const startTime = Date.now();
  const requestId = Math.random().toString(36).substr(2, 9);
  
  console.log(`[${requestId}] API Call: ${options.method || 'GET'} ${url}`);
  
  try {
    const response = await fetch(url, options);
    const duration = Date.now() - startTime;
    
    console.log(`[${requestId}] Response: ${response.status} (${duration}ms)`);
    
    if (!response.ok) {
      const errorText = await response.text();
      console.error(`[${requestId}] Error: ${errorText}`);
    }
    
    return response;
  } catch (error) {
    const duration = Date.now() - startTime;
    console.error(`[${requestId}] Failed: ${error.message} (${duration}ms)`);
    throw error;
  }
}

Webhook Monitoring

// Track webhook delivery
app.post('/webhook/:provider', async (req, res) => {
  const provider = req.params.provider;
  const webhookId = Math.random().toString(36).substr(2, 9);
  
  console.log(`[${webhookId}] Webhook received from ${provider}`);
  
  try {
    // Process webhook
    await processWebhook(provider, req.body);
    
    console.log(`[${webhookId}] Webhook processed successfully`);
    res.status(200).json({ status: 'ok', webhookId });
    
  } catch (error) {
    console.error(`[${webhookId}] Webhook processing failed:`, error);
    res.status(500).json({ 
      status: 'error', 
      webhookId, 
      error: error.message 
    });
  }
});

Next Steps


ต้องการความช่วยเหลือ? ติดต่อเราได้ที่ ShantiLink.com 💬