본문 바로가기
행복바이러스 행복바이러스

노션 Webhook 이벤트를 파싱하여 외부 데이터베이스(PostgreSQL)에 백업하는 구조

읽는 시간 약 34분

노션을 프로젝트 관리나 고객 정보 정리에 활용하다 보면 노션의 데이터가 변경될 때마다 외부 데이터베이스에도 자동으로 저장하고 싶을 때가 있습니다.

가장 단순한 방법은 일정 시간마다 Notion API를 호출하는 방식입니다. 하지만 데이터 변경 여부와 관계없이 API를 반복해서 요청하기 때문에 페이지가 많아질수록 불필요한 호출이 늘어날 수 있습니다.

이때 활용할 수 있는 기능이 Notion Webhook입니다. 페이지가 생성되거나 수정되면 노션이 외부 서버로 이벤트를 보내고, 서버는 해당 이벤트를 분석해 PostgreSQL에 데이터를 저장합니다.

이 글에서는 노션 Webhook 이벤트를 수신하고 PostgreSQL에 백업하는 전체 구조를 단계별로 살펴보겠습니다.

전체 데이터 처리 구조

노션 데이터가 PostgreSQL에 백업되는 과정은 다음과 같습니다.

  1. 사용자가 노션 페이지를 생성하거나 수정합니다.
  2. 노션이 변경 내용을 감지해 Webhook 이벤트를 전송합니다.
  3. 외부 서버가 X-Notion-Signature 값을 확인해 요청을 검증합니다.
  4. Webhook 이벤트 ID를 조회해 이미 처리한 요청인지 확인합니다.
  5. 이벤트에 포함된 페이지 ID로 Notion API를 호출합니다.
  6. 최신 페이지 속성과 블록 데이터를 가져옵니다.
  7. 가져온 데이터를 PostgreSQL에 저장하거나 갱신합니다.
  8. 이벤트 처리 결과와 오류 내역을 별도의 테이블에 기록합니다.

한 줄로 정리하면 다음과 같습니다.

노션 페이지 변경 → Webhook 수신 → 서명 검증 → 중복 확인 → Notion API 조회 → 데이터 파싱 → PostgreSQL 저장 → 처리 이력 기록

여기서 주의할 점은 Webhook 이벤트 안에 페이지의 모든 내용이 들어오는 것이 아니라는 점입니다.

Notion Webhook은 주로 어떤 객체에 변화가 발생했다는 사실과 해당 객체의 ID를 전달합니다. 따라서 이벤트를 받은 서버가 객체 ID를 이용해 Notion API를 다시 호출하고 최신 페이지 정보와 블록 내용을 가져와야 합니다.

1. 필요한 구성 요소

이번 구조에서는 다음과 같은 환경을 사용합니다.

  • Node.js 18 이상
  • Express
  • PostgreSQL
  • @notionhq/client
  • pg
  • 외부에서 접속할 수 있는 HTTPS 주소
  • Notion Integration Token
  • Webhook Verification Token

Node.js 프로젝트를 만든 후 필요한 패키지를 설치합니다.

npm install express pg dotenv @notionhq/client

민감한 인증 정보는 소스 코드에 직접 입력하지 않고 환경변수로 관리합니다.

.env 파일에는 다음과 같이 입력합니다.

PORT=3000
NOTION_TOKEN=secret_xxxxxxxxx
NOTION_WEBHOOK_TOKEN=secret_xxxxxxxxx
DATABASE_URL=postgresql://user:password@localhost:5432/notion_backup

.env 파일에는 Notion API 토큰과 PostgreSQL 접속 정보가 들어 있으므로 외부에 공개되면 안 됩니다.

Git을 사용한다면 .gitignore 파일에 다음 항목을 추가합니다.

.env
node_modules

2. PostgreSQL 테이블 설계

백업 시스템을 만들 때는 현재 페이지 상태를 저장하는 테이블과 수신한 Webhook 이벤트를 기록하는 테이블을 분리하는 것이 좋습니다.

먼저 노션 페이지 데이터를 저장할 테이블을 생성합니다.

CREATE TABLE notion_pages (
    page_id UUID PRIMARY KEY,
    workspace_id UUID,
    title TEXT,
    properties JSONB NOT NULL DEFAULT '{}'::jsonb,
    page_data JSONB NOT NULL,
    archived BOOLEAN NOT NULL DEFAULT FALSE,
    notion_created_at TIMESTAMPTZ,
    notion_updated_at TIMESTAMPTZ,
    synced_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

다음으로 Webhook 이벤트 처리 내역을 저장할 테이블을 생성합니다.

CREATE TABLE notion_webhook_events (
    event_id UUID PRIMARY KEY,
    event_type TEXT NOT NULL,
    entity_id UUID,
    workspace_id UUID,
    occurred_at TIMESTAMPTZ NOT NULL,
    attempt_number INTEGER,
    payload JSONB NOT NULL,
    processing_status TEXT NOT NULL DEFAULT 'received',
    error_message TEXT,
    processed_at TIMESTAMPTZ
);

데이터 조회 속도를 높이기 위해 인덱스도 추가합니다.

CREATE INDEX idx_notion_pages_updated
ON notion_pages (notion_updated_at DESC);

CREATE INDEX idx_webhook_status
ON notion_webhook_events (processing_status, occurred_at);

page_data와 payload를 JSONB 형식으로 저장하면 노션의 속성 구성이 변경되더라도 테이블 구조를 계속 수정하지 않고 원본 데이터를 보존할 수 있습니다.

다만 제목, 상태, 작성일처럼 검색과 통계에 자주 사용하는 값은 별도의 열로 분리하는 것이 좋습니다.

3. PostgreSQL 연결 설정

Node.js에서 PostgreSQL에 연결하기 위해 pg 패키지의 Pool을 사용합니다.

import pg from "pg";

const { Pool } = pg;

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  ssl:
    process.env.NODE_ENV === "production"
      ? { rejectUnauthorized: false }
      : false
});

Pool을 사용하면 요청이 들어올 때마다 새로운 데이터베이스 연결을 만들지 않고 기존 연결을 재사용할 수 있습니다.

다만 운영하는 PostgreSQL 서비스에 따라 SSL 설정 방식이 다를 수 있으므로 실제 배포 환경의 연결 안내를 확인해야 합니다.

4. Webhook 구독 확인하기

Notion Integration 설정에서 Webhook 주소를 등록하면 노션은 해당 주소로 verification_token이 포함된 요청을 한 번 보냅니다.

요청 구조는 다음과 비슷합니다.

{
  "verification_token": "secret_xxxxxxxxx"
}

서버에서 이 값을 확인한 후 Notion Integration의 Webhook 설정 화면으로 돌아가 Verification Token을 입력하면 구독이 활성화됩니다.

간단한 확인 코드는 다음과 같습니다.

app.post("/webhooks/notion", express.json(), (req, res) => {
  if (req.body.verification_token) {
    console.log("Verification token:", req.body.verification_token);

    return res.status(200).json({
      received: true
    });
  }

  return res.status(200).json({
    received: true
  });
});

위 코드는 초기 구독 확인 과정을 설명하기 위한 예제입니다.

실제 운영 환경에서는 Verification Token을 로그에 계속 출력하지 말고 환경변수나 비밀 관리 서비스에 안전하게 보관해야 합니다.

또한 검증이 완료된 Webhook 주소를 변경하려면 기존 구독을 삭제하고 새로운 주소로 다시 등록해야 할 수 있습니다.

5. Webhook 서명 검증하기

노션은 Webhook 요청의 X-Notion-Signature 헤더에 암호화된 서명을 포함합니다.

서버는 처음 받은 Verification Token을 사용해 서명을 확인하고, 해당 요청이 실제 노션에서 전송됐는지 검증할 수 있습니다.

import "dotenv/config";
import express from "express";
import { verifyWebhookSignature } from "@notionhq/client";

const app = express();

app.post(
  "/webhooks/notion",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    const rawBody = req.body.toString("utf8");
    const signature = req.header("x-notion-signature");

    if (!signature) {
      return res.status(401).json({
        error: "Signature header is missing"
      });
    }

    const valid = await verifyWebhookSignature({
      body: rawBody,
      signature,
      verificationToken: process.env.NOTION_WEBHOOK_TOKEN
    });

    if (!valid) {
      return res.status(401).json({
        error: "Invalid signature"
      });
    }

    const event = JSON.parse(rawBody);

    res.status(200).json({
      received: true
    });

    processEvent(event).catch(console.error);
  }
);

서명을 확인할 때는 JSON.parse()로 변환하기 전의 원본 요청 본문을 사용해야 합니다.

JSON 객체를 다시 문자열로 변환하면 공백이나 문자 배열이 달라질 수 있습니다. 원본 내용이 달라지면 정상적인 요청이라도 서명 확인에 실패할 수 있습니다.

6. Webhook 이벤트 구조 이해하기

Notion Webhook 이벤트에는 다음과 같은 공통 정보가 포함될 수 있습니다.

필드설명
idWebhook 이벤트의 고유 ID
typepage.created 등 이벤트 종류
timestamp이벤트가 발생한 시각
workspace_id이벤트가 발생한 워크스페이스 ID
entity.id변경된 페이지나 객체의 ID
attempt_numberWebhook 전송 시도 횟수
data이벤트 종류별 추가 정보

대표적인 페이지 관련 이벤트는 다음과 같습니다.

  • page.created: 새로운 페이지가 생성된 경우
  • page.properties_updated: 페이지 속성이 변경된 경우
  • page.content_updated: 페이지 본문이 변경된 경우
  • page.moved: 페이지 위치가 변경된 경우
  • page.deleted: 페이지가 휴지통으로 이동한 경우
  • page.undeleted: 삭제한 페이지가 복원된 경우

최근 Notion API에서는 데이터베이스 아래에 여러 Data Source가 존재할 수 있습니다. 따라서 다음과 같은 이벤트도 고려해야 합니다.

  • data_source.created
  • data_source.content_updated
  • data_source.schema_updated
  • data_source.deleted
  • data_source.undeleted

이벤트에서 필요한 값만 추출하는 함수를 만들어두면 이후 처리 과정이 간단해집니다.

function parseEvent(event) {
  return {
    eventId: event.id,
    eventType: event.type,
    entityId: event.entity?.id ?? null,
    workspaceId: event.workspace_id,
    occurredAt: event.timestamp,
    attemptNumber: event.attempt_number ?? 1,
    payload: event
  };
}

이렇게 가공한 데이터는 이벤트 테이블에 저장하거나 이벤트 종류별 처리 함수를 호출할 때 사용할 수 있습니다.

7. 이벤트 중복 저장 방지하기

네트워크 오류나 서버 응답 실패가 발생하면 동일한 Webhook 이벤트가 다시 전달될 수 있습니다.

같은 이벤트를 반복해서 처리하면 중복 데이터가 생기거나 불필요한 Notion API 호출이 발생할 수 있습니다.

이벤트의 id 값을 PostgreSQL의 기본키로 지정하면 이미 처리한 이벤트인지 확인할 수 있습니다.

async function saveEvent(client, parsed) {
  const result = await client.query(
    `INSERT INTO notion_webhook_events
      (
        event_id,
        event_type,
        entity_id,
        workspace_id,
        occurred_at,
        attempt_number,
        payload
      )
     VALUES ($1, $2, $3, $4, $5, $6, $7)
     ON CONFLICT (event_id) DO NOTHING
     RETURNING event_id`,
    [
      parsed.eventId,
      parsed.eventType,
      parsed.entityId,
      parsed.workspaceId,
      parsed.occurredAt,
      parsed.attemptNumber,
      parsed.payload
    ]
  );

  return result.rowCount === 1;
}

ON CONFLICT DO NOTHING을 적용하면 동일한 이벤트 ID가 이미 저장돼 있을 때 새로운 행을 만들지 않습니다.

이처럼 같은 요청이 여러 번 들어와도 결과가 달라지지 않도록 만드는 방식을 멱등성 처리라고 합니다.

8. Notion API로 최신 페이지 조회하기

Webhook 이벤트에는 페이지 전체 내용이 포함되지 않을 수 있습니다.

따라서 페이지 생성이나 수정 이벤트를 받으면 entity.id를 이용해 Notion API에서 최신 페이지 정보를 조회해야 합니다.

import { Client } from "@notionhq/client";

const notion = new Client({
  auth: process.env.NOTION_TOKEN,
  notionVersion: "2026-03-11"
});

async function fetchPage(pageId) {
  const page = await notion.pages.retrieve({
    page_id: pageId
  });

  const blocks = [];
  let cursor;

  do {
    const response = await notion.blocks.children.list({
      block_id: pageId,
      start_cursor: cursor,
      page_size: 100
    });

    blocks.push(...response.results);

    cursor = response.has_more
      ? response.next_cursor
      : undefined;
  } while (cursor);

  return {
    page,
    blocks
  };
}

pages.retrieve는 페이지 속성을 가져오는 데 사용합니다.

blocks.children.list는 페이지 안의 텍스트, 제목, 이미지, 목록 등 블록 데이터를 가져오는 데 사용합니다.

한 번의 요청으로 모든 블록을 가져오지 못할 수 있기 때문에 has_more와 next_cursor를 확인해 다음 페이지를 계속 요청해야 합니다.

페이지 안에 접기 블록이나 하위 페이지처럼 중첩된 블록이 있다면 해당 블록 ID를 다시 조회하는 재귀 처리도 필요할 수 있습니다.

9. 페이지 제목 추출하기

노션 페이지 제목은 일반 문자열이 아니라 title 유형의 속성 안에 배열 형태로 저장됩니다.

다음 함수를 사용하면 페이지 속성에서 제목을 추출할 수 있습니다.

function extractTitle(properties = {}) {
  const titleProperty = Object.values(properties).find(
    property => property.type === "title"
  );

  return (
    titleProperty?.title
      ?.map(item => item.plain_text)
      .join("") || "제목 없음"
  );
}

페이지마다 제목 속성의 이름이 Name, 제목, 프로젝트명 등으로 다를 수 있으므로 속성 이름을 고정해서 찾는 것보다 type === "title" 조건으로 찾는 것이 안정적입니다.

10. PostgreSQL에 페이지 저장하기

가져온 페이지와 블록 데이터는 PostgreSQL에 저장합니다.

기존 데이터가 없으면 새로 추가하고, 이미 존재하는 페이지라면 최신 데이터로 갱신하도록 UPSERT 방식을 사용합니다.

async function upsertPage(client, workspaceId, data) {
  const { page, blocks } = data;
  const title = extractTitle(page.properties);

  await client.query(
    `INSERT INTO notion_pages (
       page_id,
       workspace_id,
       title,
       properties,
       page_data,
       archived,
       notion_created_at,
       notion_updated_at,
       synced_at
     )
     VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW())
     ON CONFLICT (page_id)
     DO UPDATE SET
       workspace_id = EXCLUDED.workspace_id,
       title = EXCLUDED.title,
       properties = EXCLUDED.properties,
       page_data = EXCLUDED.page_data,
       archived = EXCLUDED.archived,
       notion_updated_at = EXCLUDED.notion_updated_at,
       synced_at = NOW()`,
    [
      page.id,
      workspaceId,
      title,
      page.properties,
      { page, blocks },
      page.archived || page.in_trash || false,
      page.created_time,
      page.last_edited_time
    ]
  );
}

properties에는 데이터베이스 속성을 저장하고, page_data에는 페이지 원본과 블록 목록을 함께 저장합니다.

이렇게 구성하면 특정 속성을 빠르게 조회하면서도 노션에서 받은 원본 데이터를 보존할 수 있습니다.

11. 이벤트 종류별 처리 로직 만들기

이제 이벤트 저장, 중복 확인, Notion API 조회 및 PostgreSQL 저장 과정을 하나로 연결합니다.

async function processEvent(event) {
  const client = await pool.connect();

  try {
    const parsed = parseEvent(event);
    const isNew = await saveEvent(client, parsed);

    if (!isNew) {
      return;
    }

    const syncTypes = new Set([
      "page.created",
      "page.properties_updated",
      "page.content_updated",
      "page.moved",
      "page.undeleted"
    ]);

    if (syncTypes.has(parsed.eventType) && parsed.entityId) {
      const pageData = await fetchPage(parsed.entityId);

      await upsertPage(
        client,
        parsed.workspaceId,
        pageData
      );
    }

    if (
      parsed.eventType === "page.deleted" &&
      parsed.entityId
    ) {
      await client.query(
        `UPDATE notion_pages
         SET archived = TRUE,
             synced_at = NOW()
         WHERE page_id = $1`,
        [parsed.entityId]
      );
    }

    await client.query(
      `UPDATE notion_webhook_events
       SET processing_status = 'completed',
           processed_at = NOW(),
           error_message = NULL
       WHERE event_id = $1`,
      [parsed.eventId]
    );
  } catch (error) {
    await client.query(
      `UPDATE notion_webhook_events
       SET processing_status = 'failed',
           error_message = $2
       WHERE event_id = $1`,
      [
        event.id,
        error.message
      ]
    );

    throw error;
  } finally {
    client.release();
  }
}

페이지 삭제 이벤트가 발생했다고 PostgreSQL의 데이터까지 바로 삭제하는 것은 권장하지 않습니다.

archived 상태만 변경하면 실수로 삭제된 페이지를 확인할 수 있고, 변경 이력을 분석하거나 데이터를 복구할 때도 유용합니다.

12. 전체 서버 실행 코드

서버 실행에 필요한 기본 코드는 다음과 같습니다.

import "dotenv/config";
import express from "express";
import pg from "pg";
import { Client, verifyWebhookSignature } from "@notionhq/client";

const { Pool } = pg;

const app = express();

const pool = new Pool({
  connectionString: process.env.DATABASE_URL
});

const notion = new Client({
  auth: process.env.NOTION_TOKEN,
  notionVersion: "2026-03-11"
});

app.get("/health", (req, res) => {
  res.status(200).json({
    status: "ok"
  });
});

app.post(
  "/webhooks/notion",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    try {
      const rawBody = req.body.toString("utf8");
      const signature = req.header("x-notion-signature");

      if (!signature) {
        return res.status(401).json({
          error: "Signature header is missing"
        });
      }

      const valid = await verifyWebhookSignature({
        body: rawBody,
        signature,
        verificationToken:
          process.env.NOTION_WEBHOOK_TOKEN
      });

      if (!valid) {
        return res.status(401).json({
          error: "Invalid signature"
        });
      }

      const event = JSON.parse(rawBody);

      res.status(200).json({
        received: true
      });

      processEvent(event).catch(error => {
        console.error("Webhook processing failed:", error);
      });
    } catch (error) {
      console.error("Webhook parsing failed:", error);

      return res.status(400).json({
        error: "Invalid request"
      });
    }
  }
);

const port = process.env.PORT || 3000;

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

실제 프로젝트에서는 앞에서 작성한 parseEvent, saveEvent, fetchPage, extractTitle, upsertPage, processEvent 함수를 같은 파일에 추가하거나 각각의 모듈로 분리해야 합니다.

13. 운영 환경에서는 작업 큐가 필요한 이유

Webhook 요청을 받은 연결 안에서 Notion API 호출과 PostgreSQL 저장을 모두 처리하면 응답 시간이 길어질 수 있습니다.

안정적인 운영 구조는 다음과 같습니다.

  1. Webhook 요청의 서명을 검증합니다.
  2. 이벤트 원본을 데이터베이스 또는 작업 큐에 저장합니다.
  3. 노션에 HTTP 200 응답을 빠르게 반환합니다.
  4. 별도의 작업 프로세스가 Notion API를 호출합니다.
  5. 처리에 실패한 이벤트만 다시 실행합니다.

Redis와 BullMQ, AWS SQS 같은 작업 큐를 사용할 수 있습니다.

처리량이 많지 않은 개인 프로젝트라면 PostgreSQL의 이벤트 테이블을 간단한 작업 큐처럼 활용할 수도 있습니다.

예를 들어 processing_status가 received 또는 failed인 이벤트만 정기적으로 조회해 다시 처리하도록 구성할 수 있습니다.

14. Webhook 처리 과정에서 자주 발생하는 실수

Webhook 데이터만 저장하는 경우

Webhook에는 페이지 전체 본문이 포함되지 않을 수 있습니다.

Webhook 이벤트를 저장하는 작업과 Notion API에서 최신 데이터를 가져와 백업하는 작업을 구분해야 합니다.

동일 이벤트를 여러 번 처리하는 경우

네트워크 오류나 응답 지연으로 같은 이벤트가 다시 들어올 수 있습니다.

event.id를 기본키로 저장하고 중복 이벤트를 무시하도록 구성해야 합니다.

이벤트 도착 순서를 그대로 믿는 경우

Webhook 이벤트는 실제 변경 순서와 다르게 도착할 수 있습니다.

중요한 데이터라면 이벤트의 timestamp와 페이지의 last_edited_time을 비교하고, 최종 상태는 Notion API에서 다시 조회하는 것이 안전합니다.

API 페이지네이션을 처리하지 않는 경우

페이지 안에 블록이 많으면 한 번의 API 요청으로 전체 데이터를 가져오지 못합니다.

has_more와 next_cursor를 확인해 모든 결과를 가져와야 합니다.

토큰을 소스 코드에 입력하는 경우

Notion Integration Token과 Verification Token을 코드에 직접 작성하면 저장소나 로그를 통해 노출될 수 있습니다.

반드시 환경변수나 비밀 관리 서비스를 사용해야 합니다.

삭제 이벤트에서 데이터를 바로 지우는 경우

PostgreSQL 데이터를 바로 삭제하면 이전 상태를 확인하거나 복구하기 어렵습니다.

실제 행을 지우는 대신 archived 상태를 변경하는 소프트 삭제 방식을 사용하는 것이 좋습니다.

모든 노션 페이지가 백업될 것으로 생각하는 경우

Notion Integration에 접근 권한이 없는 페이지는 API로 조회할 수 없습니다.

백업 대상 페이지나 데이터베이스가 Integration에 정상적으로 공유돼 있는지 먼저 확인해야 합니다.

15. 단순 백업과 변경 이력 백업의 차이

앞에서 작성한 notion_pages 테이블은 각 페이지의 최신 상태만 저장합니다.

페이지가 수정될 때마다 이전 데이터를 덮어쓰기 때문에 과거 버전까지 보관하려면 별도의 이력 테이블이 필요합니다.

CREATE TABLE notion_page_history (
    history_id BIGSERIAL PRIMARY KEY,
    page_id UUID NOT NULL,
    event_id UUID,
    title TEXT,
    properties JSONB NOT NULL,
    page_data JSONB NOT NULL,
    notion_updated_at TIMESTAMPTZ,
    backed_up_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

페이지를 갱신하기 전에 현재 데이터를 이력 테이블에 추가하면 변경 전 내용을 보관할 수 있습니다.

다만 모든 수정 내역을 계속 저장하면 데이터 용량이 빠르게 증가할 수 있습니다. 중요한 페이지에만 적용하거나 일정 기간이 지난 이력은 별도 저장소로 이동하는 정책이 필요합니다.

16. 데이터 보존 정책도 함께 정해야 한다

백업 시스템은 데이터를 저장하는 것만으로 끝나지 않습니다.

다음과 같은 운영 기준을 함께 정해야 합니다.

  • Webhook 원본 이벤트 보관 기간
  • 페이지 변경 이력 보관 기간
  • 실패 이벤트 재시도 횟수
  • 개인정보가 포함된 속성의 암호화 여부
  • 데이터베이스 접근 권한
  • 정기적인 PostgreSQL 백업 일정
  • 오래된 로그의 자동 삭제 기준

PostgreSQL에 저장했다고 해서 완전한 백업이 끝나는 것은 아닙니다.

PostgreSQL 자체에 장애가 발생할 수 있으므로 정기적인 스냅샷이나 별도 저장소 백업도 함께 준비해야 합니다.

마무리

Notion Webhook은 노션 데이터를 그대로 전달하는 완전한 백업 기능이 아니라 변경 사실을 외부 서버에 알려주는 신호에 가깝습니다.

안정적인 노션 백업 시스템을 만들려면 다음 네 가지가 핵심입니다.

  • HMAC 서명을 통한 Webhook 요청 검증
  • 이벤트 ID를 이용한 중복 처리 방지
  • Notion API를 통한 최신 데이터 조회
  • PostgreSQL의 JSONB와 UPSERT를 활용한 데이터 저장

여기에 이벤트 로그, 실패 재처리 및 변경 이력 테이블까지 추가하면 노션 페이지가 변경될 때마다 PostgreSQL에 최신 상태를 보관하면서 언제 어떤 이벤트가 발생했는지도 함께 추적할 수 있습니다.

처음부터 복잡한 메시지 큐를 도입하기보다는 Webhook 수신, 이벤트 기록, 페이지 조회, PostgreSQL 저장 순서로 작은 구조부터 구현하는 것이 좋습니다. 이후 페이지와 이벤트가 많아지면 작업 큐와 재시도 시스템을 단계적으로 추가하면 됩니다.

youngji
함께 보면 좋은 글

댓글 0

첫 댓글을 남겨보세요.

광고 차단 알림

광고 클릭 제한을 초과하여 광고가 차단되었습니다.

단시간에 반복적인 광고 클릭은 시스템에 의해 감지되며, IP가 수집되어 사이트 관리자가 확인 가능합니다.