SuitsDocs
레퍼런스개발자 레퍼런스

웹훅

슈츠에서 발생하는 이벤트를 실시간으로 수신합니다.

웹훅 개요

동작 방식

슈츠에서 이벤트 발생 (행 생성 등)

웹훅 시스템이 이벤트 감지

등록된 엔드포인트로 HTTP POST 요청

외부 시스템에서 이벤트 처리

활용 예시

  • 외부 시스템 연동: 새 주문이 등록되면 ERP에 자동 동기화
  • 알림: 중요 데이터 변경 시 Slack/이메일 알림
  • 백업: 데이터 변경 시 외부 데이터베이스에 백업
  • 워크플로우: 외부 자동화 도구(Zapier, Make) 연동

웹훅 설정

1단계: 웹훅 엔드포인트 등록

  1. 워크스페이스 설정개발자 설정
  2. 웹훅 탭 선택
  3. + 새 웹훅 추가 클릭

2단계: 설정 입력

설정설명예시
이름웹훅 식별 이름"주문 알림"
URL이벤트를 수신할 엔드포인트https://api.myapp.com/webhooks/suits
이벤트구독할 이벤트 유형row.created, row.updated
데이터모델특정 데이터모델만 구독 (선택)주문, 고객

3단계: 시크릿 키 저장

웹훅 시크릿 키는 한 번만 표시됩니다. 요청 서명 검증에 필요하니 안전하게 저장하세요.

whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

이벤트 유형

데이터모델 (Row) 이벤트

이벤트설명트리거 시점
row.created새 행 생성됨행이 처음 생성될 때
row.updated행 수정됨행의 속성이 변경될 때
row.deleted행 삭제됨행이 삭제될 때

페이지 이벤트

이벤트설명트리거 시점
page.created새 페이지 생성됨페이지가 생성될 때
page.updated페이지 수정됨페이지 내용이 변경될 때
page.deleted페이지 삭제됨페이지가 삭제될 때

워크스페이스 이벤트

이벤트설명트리거 시점
member.invited멤버 초대됨새 멤버 초대 시
member.removed멤버 제거됨멤버 삭제 시

페이로드 형식

웹훅 요청은 HTTP POST로 전송되며, JSON 형식의 페이로드를 포함합니다.

row.created

{
  "id": "evt_abc123def456",
  "type": "row.created",
  "createdAt": "2024-03-22T10:30:00Z",
  "workspaceId": "ws_xxxxx",
  "data": {
    "datamodelId": "dm_abc123",
    "datamodelName": "고객",
    "rowId": "row_xyz789",
    "properties": {
      "회사명": "테크스타트",
      "이메일": "[email protected]",
      "상태": "lead",
      "계약금액": 50000000
    },
    "createdBy": {
      "id": "user_001",
      "name": "김담당",
      "email": "[email protected]"
    }
  }
}

row.updated

{
  "id": "evt_def456ghi789",
  "type": "row.updated",
  "createdAt": "2024-03-22T11:00:00Z",
  "workspaceId": "ws_xxxxx",
  "data": {
    "datamodelId": "dm_abc123",
    "datamodelName": "고객",
    "rowId": "row_xyz789",
    "changes": {
      "상태": {
        "before": "lead",
        "after": "active"
      },
      "계약금액": {
        "before": 50000000,
        "after": 55000000
      }
    },
    "updatedBy": {
      "id": "user_002",
      "name": "이영업",
      "email": "[email protected]"
    }
  }
}

row.deleted

{
  "id": "evt_ghi789jkl012",
  "type": "row.deleted",
  "createdAt": "2024-03-22T12:00:00Z",
  "workspaceId": "ws_xxxxx",
  "data": {
    "datamodelId": "dm_abc123",
    "datamodelName": "고객",
    "rowId": "row_xyz789",
    "deletedBy": {
      "id": "user_001",
      "name": "김담당"
    }
  }
}

요청 헤더

웹훅 요청에는 다음 헤더가 포함됩니다:

POST /webhooks/suits HTTP/1.1
Host: api.myapp.com
Content-Type: application/json
User-Agent: Suits-Webhook/1.0
X-Suits-Webhook-Id: wh_xxxxx
X-Suits-Event-Id: evt_abc123def456
X-Suits-Event-Type: row.created
X-Suits-Signature: sha256=xxxxxxxx
X-Suits-Timestamp: 1711101000

서명 검증

모든 웹훅 요청은 서명이 포함됩니다. 서명을 검증하여 요청이 슈츠에서 온 것인지 확인하세요.

서명 생성 방식

signature = HMAC-SHA256(timestamp + "." + payload, secret)

Node.js 검증 예시

const crypto = require('crypto');

function verifyWebhookSignature(req, secret) {
  const signature = req.headers['x-suits-signature'];
  const timestamp = req.headers['x-suits-timestamp'];
  const payload = JSON.stringify(req.body);
  
  // 서명 검증
  const signedPayload = `${timestamp}.${payload}`;
  const expectedSignature = 'sha256=' + 
    crypto.createHmac('sha256', secret)
      .update(signedPayload)
      .digest('hex');
  
  // 타이밍 세이프 비교
  const isValid = crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
  
  // 타임스탬프 검증 (5분 이내)
  const currentTime = Math.floor(Date.now() / 1000);
  const isRecent = currentTime - parseInt(timestamp) < 300;
  
  return isValid && isRecent;
}

// Express 미들웨어 예시
app.post('/webhooks/suits', (req, res) => {
  const isValid = verifyWebhookSignature(req, process.env.WEBHOOK_SECRET);
  
  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  // 이벤트 처리
  const event = req.body;
  console.log(`Received ${event.type} event`);
  
  // 즉시 200 응답 (처리는 비동기로)
  res.status(200).json({ received: true });
  
  // 비동기 처리
  processEvent(event);
});

Python 검증 예시

import hmac
import hashlib
import time

def verify_webhook_signature(payload, signature, timestamp, secret):
    # 서명 생성
    signed_payload = f"{timestamp}.{payload}"
    expected_signature = 'sha256=' + hmac.new(
        secret.encode(),
        signed_payload.encode(),
        hashlib.sha256
    ).hexdigest()
    
    # 비교
    is_valid = hmac.compare_digest(signature, expected_signature)
    
    # 타임스탬프 검증
    is_recent = time.time() - int(timestamp) < 300
    
    return is_valid and is_recent

재시도 정책

웹훅 요청이 실패하면 자동으로 재시도합니다.

재시도 스케줄

시도대기 시간누적 시간
1차1분1분
2차5분6분
3차30분36분
4차2시간2시간 36분
5차24시간26시간 36분

실패 조건

다음 경우 요청이 실패한 것으로 간주합니다:

  • 5초 이내 응답 없음 (타임아웃)
  • HTTP 상태 코드가 2xx가 아닌 경우
  • 연결 불가

5회 모두 실패하면 해당 이벤트는 폐기됩니다. 개발자 설정에서 실패 로그를 확인할 수 있습니다.


베스트 프랙티스

1. 빠른 응답

웹훅 엔드포인트는 5초 이내에 응답해야 합니다.

// ✅ 좋은 예: 즉시 응답, 비동기 처리
app.post('/webhook', (req, res) => {
  res.status(200).json({ received: true });
  
  // 비동기로 처리
  processEventAsync(req.body);
});

// ❌ 나쁜 예: 동기 처리 후 응답
app.post('/webhook', async (req, res) => {
  await heavyDatabaseOperation(); // 시간이 오래 걸림
  res.status(200).json({ done: true }); // 타임아웃 위험
});

2. 멱등성 보장

같은 이벤트가 여러 번 전송될 수 있습니다. 이벤트 ID로 중복을 방지하세요.

const processedEvents = new Set();

app.post('/webhook', (req, res) => {
  const eventId = req.body.id;
  
  // 이미 처리한 이벤트 스킵
  if (processedEvents.has(eventId)) {
    return res.status(200).json({ status: 'already_processed' });
  }
  
  processedEvents.add(eventId);
  processEvent(req.body);
  
  res.status(200).json({ received: true });
});

3. 로깅

모든 웹훅 요청을 로깅하여 디버깅에 활용하세요.

app.post('/webhook', (req, res) => {
  console.log({
    eventId: req.body.id,
    eventType: req.body.type,
    timestamp: new Date().toISOString(),
    payload: req.body
  });
  
  res.status(200).json({ received: true });
});

4. 에러 처리

처리 중 에러가 발생해도 200을 반환하되, 내부적으로 에러를 기록하세요.

app.post('/webhook', async (req, res) => {
  res.status(200).json({ received: true });
  
  try {
    await processEvent(req.body);
  } catch (error) {
    // 에러 로깅 (재시도하지 않음)
    console.error('Webhook processing error:', error);
    await alertTeam(error); // 팀에게 알림
  }
});

테스트

웹훅 테스트 도구

개발자 설정에서 테스트 이벤트 전송 버튼으로 웹훅을 테스트할 수 있습니다.

로컬 개발 테스트

로컬 환경에서 테스트하려면 터널링 서비스를 사용하세요:

# ngrok 사용
ngrok http 3000

# 출력된 URL을 웹훅 엔드포인트로 등록
# https://abc123.ngrok.io/webhooks/suits

웹훅 로그

개발자 설정에서 최근 웹훅 요청 로그를 확인할 수 있습니다:

  • 이벤트 ID
  • 이벤트 유형
  • 요청 시간
  • 응답 상태
  • 재시도 횟수
  • 페이로드 (클릭하여 상세 확인)

문제 해결

웹훅이 수신되지 않음

  1. 엔드포인트 URL이 올바른지 확인
  2. 서버가 외부에서 접근 가능한지 확인
  3. 방화벽/보안 그룹 설정 확인
  4. 구독한 이벤트 유형 확인

서명 검증 실패

  1. 시크릿 키가 올바른지 확인
  2. payload를 raw string으로 사용하는지 확인
  3. 타임스탬프 형식 확인

타임아웃 발생

  1. 엔드포인트가 5초 이내에 응답하는지 확인
  2. 무거운 작업은 비동기로 처리
  3. 큐 시스템 도입 고려 (Redis, SQS 등)

다음 단계