CMS

웹훅(Webhook)으로 CMS 연동하기 — ND소프트 등 자체 CMS 자동 발행

ND소프트처럼 직접 연동이 없는 CMS도 웹훅으로 DotAI Writer와 연동해 AI 기사를 자동 발행할 수 있습니다. 페이로드 구조·HMAC 서명·PHP 수신 샘플 코드까지 개발팀 전달용으로 정리했습니다.

#웹훅 #Webhook #ND소프트 #CMS 연동 #HMAC #자동 발행

ND소프트처럼 DotAI Writer에 직접 연동 메뉴가 없는 CMS도 웹훅(Webhook) 방식으로 연동할 수 있습니다. CMS 개발팀이 웹훅 수신 엔드포인트를 한 번만 구현하면, 이후 AI가 생성한 기사가 자동으로 해당 CMS에 등록됩니다.

ℹ️ 참고

이 글의 ‘개발팀 전달용’ 섹션(페이로드 구조·서명 규칙·PHP 샘플 코드)을 그대로 CMS 개발팀에 전달하시면 됩니다. 시크릿 키는 아래 절차로 생성해 함께 전달하세요.

STEP 1
시크릿 키 생성
DotAI에서 발급·복사
소요: 1분
STEP 2
개발팀 전달
본 문서 + 시크릿 키
소요: —
STEP 3
웹훅 URL 등록
전달받은 URL 추가
소요: 1분
STEP 4
자동 발행
기사 생성 시 자동 전송
소요: 자동

개발팀 전달용 — 페이로드 & 서명 규칙

DotAI Writer는 기사 발행 시 아래 JSON을 웹훅 URL로 HTTP POST 전송합니다. 수신 측에서 이를 받아 CMS에 등록하면 됩니다.

① 연결 테스트 요청 (ping)

{
  "event": "ping",
  "timestamp": "2026-02-19T12:00:00.000Z"
}

연결 테스트는 2xx 응답만 반환하면 성공으로 간주됩니다.

② 기사 발행 요청 (article.published)

{
  "event": "article.published",
  "timestamp": "2026-02-19T12:00:00.000Z",
  "tenant_id": 123,
  "article": {
    "id": 456,
    "title": "기사 제목",
    "content": "<p>HTML 본문...</p>",
    "summary": "요약문 (~300자)",
    "category": "정치",
    "tags": ["태그1", "태그2"],
    "status": "publish",
    "writer_name": "홍길동",
    "writer_email": "writer@example.com"
  }
}
  • statuspublish(즉시 공개) 또는 draft(초안 저장, 비공개)
  • category — 전체 카테고리: 정치 · 사회 · 경제 · 국제 · 스포츠 · 연예 · 문화 · IT·과학 · 사설·칼럼 · 종합

③ 보안 헤더 (시크릿 키 설정 시)

헤더설명
X-DotAI-SignatureHMAC-SHA256 서명 (sha256=<hex>). 서명 대상 = 요청 raw body 전체
X-DotAI-Timestamp요청 Unix 타임스탬프(초). 리플레이 공격 방지용(±5분 권장). ping 테스트엔 없을 수 있음

시크릿 키 생성하기

1단계 — CMS 연동 페이지로 이동

DotAI Writer CMS 연동 페이지
CMS 연동 페이지

2단계 — [+ 연동 추가] 클릭

연동 추가
[+ 연동 추가]

3단계 — [웹훅] 선택 후 시크릿 키 생성·복사

연동 타입을 [웹훅] 으로 선택한 뒤 시크릿 키를 [생성][복사] 하여 CMS 개발팀에 전달합니다.

웹훅 선택 + 시크릿 키 생성
웹훅 선택 후 시크릿 키 생성·복사

개발 완료 후 — 웹훅 URL 등록 & 테스트

4단계 — 웹훅 URL + 시크릿 키 입력

CMS 개발팀으로부터 전달받은 웹훅 URL 과 앞서 생성한 시크릿 키 를 입력하고 [추가] 합니다.

웹훅 URL 입력
전달받은 웹훅 URL + 시크릿 키 입력

5단계 — 연결 테스트

⚠️ 주의

테스트를 완료해야 CMS 연동이 가능합니다. 꼭 테스트 버튼을 눌러 완료해 주세요.

연동 테스트
테스트 버튼으로 연동 확인

연동한 CMS를 AI 기자에 설정하기

6단계 — AI 기자 생성/수정

AI 기자 생성/수정
AI 기자 생성 또는 수정

7단계 — 게시 설정 + 연동 CMS(웹훅) 선택

게시 설정에서 [자동 검수(Draft)] 또는 [자동 게시] 를 선택하고 연동 CMS로 웹훅을 선택합니다.

게시 설정 + CMS 선택
게시 설정에서 웹훅 선택
💡

이제 AI 기자가 기사를 생성하면 연동한 CMS에 자동으로 등록됩니다.

PHP 수신 샘플 코드 (참고용)

CMS 개발팀이 바로 적용할 수 있도록 작성한 참고 예시입니다. 실제 운영 환경의 DB 스키마·인코딩·인증 정책에 맞게 수정해 사용하세요. PHP 7.4 이상 권장.

서명 검증 규칙 요약

  • 서명 대상: HTTP 요청의 raw body 전체(JSON 문자열 그대로)
  • 알고리즘: HMAC-SHA256, 출력: 소문자 hex
  • 헤더: X-DotAI-Signature: sha256=<hex>
  • 타임스탬프(X-DotAI-Timestamp)는 별도 헤더이며 서명 대상에는 포함되지 않습니다. ±5분 정도 허용을 권장합니다. ping 테스트는 헤더가 없을 수 있으므로, 헤더가 있을 때만 검증하세요.
<?php
/**
 * DotAI Writer Webhook Receiver (참고용 샘플)
 *  - PHP 7.4+
 *  - 환경에 맞게 시크릿 키 보관 위치와 DB 저장 로직을 수정해 사용하세요.
 */

// ── 설정 ──────────────────────────────────────────────
$SECRET_KEY          = 'YOUR_SECRET_KEY_HERE'; // 도트AI에서 발급받은 시크릿 키
$TIMESTAMP_TOLERANCE = 300;                    // 허용 시차(초). 기본 ±5분

// ── 1. 원본 바디 읽기 (서명 검증은 raw body 기준) ──────
$body = file_get_contents('php://input');
if ($body === false || $body === '') {
    http_response_code(400);
    exit(json_encode(['error' => 'empty body']));
}

// ── 2. 서명 검증 ──────────────────────────────────────
$signatureHeader = $_SERVER['HTTP_X_DOTAI_SIGNATURE'] ?? '';
$timestampHeader = $_SERVER['HTTP_X_DOTAI_TIMESTAMP'] ?? '';

if ($SECRET_KEY !== '') {
    if (strpos($signatureHeader, 'sha256=') !== 0) {
        http_response_code(401);
        exit(json_encode(['error' => 'missing or invalid signature header']));
    }

    $sentSig     = substr($signatureHeader, 7); // "sha256=" 제거
    $expectedSig = hash_hmac('sha256', $body, $SECRET_KEY);

    // 타이밍 공격 방지 — 반드시 hash_equals 사용
    if (!hash_equals($expectedSig, $sentSig)) {
        http_response_code(401);
        exit(json_encode(['error' => 'invalid signature']));
    }

    // 타임스탬프 검증 (헤더가 있을 때만 — ping 테스트는 없을 수 있음)
    if ($timestampHeader !== '') {
        $diff = abs(time() - (int)$timestampHeader);
        if ($diff > $TIMESTAMP_TOLERANCE) {
            http_response_code(401);
            exit(json_encode(['error' => 'timestamp out of tolerance']));
        }
    }
}

// ── 3. JSON 파싱 ──────────────────────────────────────
$data = json_decode($body, true);
if (!is_array($data) || empty($data['event'])) {
    http_response_code(400);
    exit(json_encode(['error' => 'invalid payload']));
}

// ── 4. 이벤트 분기 ────────────────────────────────────
header('Content-Type: application/json; charset=utf-8');

switch ($data['event']) {

    // 연결 테스트 — 2xx 응답만 하면 성공으로 간주됩니다.
    case 'ping':
        http_response_code(200);
        echo json_encode(['ok' => true, 'event' => 'ping']);
        break;

    // 기사 발행
    case 'article.published':
        $article = $data['article'] ?? [];

        if (empty($article['id']) || empty($article['title']) || !isset($article['content'])) {
            http_response_code(400);
            exit(json_encode(['error' => 'missing required article fields']));
        }

        $title       = $article['title'];
        $contentHtml = $article['content'];                       // HTML 본문
        $summary     = $article['summary']      ?? '';
        $category    = $article['category']     ?? '종합';
        $tags        = isset($article['tags']) && is_array($article['tags'])
                       ? implode(',', $article['tags']) : '';
        $writerName  = $article['writer_name']  ?? '';
        $writerEmail = $article['writer_email'] ?? '';
        $isDraft     = (($article['status'] ?? 'publish') === 'draft');

        // ── DB 저장 예시 (PDO) — 실제 테이블/컬럼명에 맞게 수정 ──
        // $stmt = $pdo->prepare(
        //     "INSERT INTO articles
        //        (title, content, summary, category, tags,
        //         writer_name, writer_email, is_published, created_at)
        //      VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW())"
        // );
        // $stmt->execute([
        //     $title, $contentHtml, $summary, $category, $tags,
        //     $writerName, $writerEmail, $isDraft ? 0 : 1,
        // ]);

        error_log(sprintf(
            '[DotAI] article.published id=%s status=%s title=%s',
            $article['id'], $isDraft ? 'draft' : 'publish', $title
        ));

        http_response_code(200);
        echo json_encode(['ok' => true, 'received_id' => $article['id']]);
        break;

    default:
        http_response_code(400);
        echo json_encode(['error' => 'unknown event: ' . $data['event']]);
}
ℹ️ 참고

구현 시 자주 빠뜨리는 부분: ① php://input 결과를 변형하지 말고 있는 그대로 서명 검증에 사용 ② 시그니처 비교는 반드시 hash_equals()status: "draft" 면 비공개 저장, publish 면 즉시 공개로 분기 ④ 웹훅 URL은 HTTPS 로 노출 ⑤ 응답은 2xx로 빠르게 반환(장시간 처리는 큐로 위임).

자주 묻는 질문

ND소프트 외 다른 CMS도 웹훅으로 연동되나요? +

네. 웹훅은 범용 방식이라 위 페이로드를 수신해 처리할 수 있는 모든 CMS·자동화 파이프라인과 연동할 수 있습니다.

시크릿 키 없이도 연동할 수 있나요? +

가능하지만 권장하지 않습니다. 시크릿 키를 설정하면 HMAC-SHA256 서명으로 요청 위변조를 검증할 수 있어 안전합니다.

개발팀에 무엇을 전달하면 되나요? +

이 글의 ‘개발팀 전달용’ 섹션(페이로드·서명 규칙)과 PHP 샘플 코드, 그리고 DotAI에서 생성한 시크릿 키를 전달하면 됩니다.

CMS 연동 방식 전체 비교는 CMS 연동으로 기사 자동 발행하기에서, 도메인만 입력하는 간편 연동은 뉴스브릿지 CMS 연동 방법에서 확인하세요.

DotAI Writer를 직접 체험해 보세요

AI 기사 생성, 이미지 제작, CMS 자동 발행까지 한 곳에서.

무료로 시작하기