Tips & Tricks (업데이트: 2026. 6. 3.)

Claude Code로 SaaS 연동 만들기: API 키, OAuth, Webhook, 감사 로그

Claude Code로 SaaS를 연동하는 실전 가이드. API 키, OAuth, Webhook, 재시도, 시크릿, 감사 로그까지 다룹니다.

Claude Code로 SaaS 연동 만들기: API 키, OAuth, Webhook, 감사 로그

Claude Code로 SaaS 연동을 만들 때 첫 질문은 “어떤 엔드포인트를 호출할까?”가 아닙니다. 먼저 안전하게 권한을 부여하고, 실패 후 재시도해도 중복 작업이 생기지 않게 하며, 나중에 누가 무엇을 했는지 설명할 수 있어야 합니다. 이 층을 건너뛰면 단순한 Slack 알림도 보안, 과금, 운영 문제가 될 수 있습니다.

이 글은 API 키, OAuth, Webhook, rate limit, retry, idempotency, audit log, secret, test environment를 Claude Code에서 쓰기 좋은 실전 흐름으로 정리합니다. Webhook은 SaaS가 내 서버로 보내는 이벤트입니다. Idempotency는 같은 작업을 다시 실행해도 결과가 중복되지 않는 성질입니다. Audit log는 자동화가 무엇을 했는지 나중에 설명하기 위한 기록입니다.

예제는 Node.js 20 이상을 기준으로 하며, 작은 scripts/ 폴더에 그대로 복사해 사용할 수 있습니다.

연동 아키텍처

연동 상태를 Claude Code 안에만 두지 마세요. Claude Code는 계획, 코드 생성, 명령 실행에 강합니다. 토큰, 재시도 상태, 감사 이력은 connector 코드에 두는 편이 안정적입니다.

flowchart LR
  A[Claude Code] --> B[CLI or MCP connector]
  B --> C[Auth and secret store]
  B --> D[Retry and rate-limit wrapper]
  D --> E[SaaS API]
  E --> F[Webhook receiver]
  F --> G[Queue]
  G --> H[Worker]
  H --> I[Audit log]

가장 작은 실용 형태는 CLI wrapper입니다. 예를 들어 Claude Code가 node scripts/slack-notify.mjs를 실행하게 합니다. 자주 쓰는 workflow라면 MCP server로 올려 입력 스키마, 권한, 에러 처리를 재사용하세요. Webhook receiver는 구현 비용이 더 크지만 Stripe, GitHub, Slack 같은 SaaS가 workflow를 시작해야 할 때 필요합니다.

네 가지 구체적인 사용 사례

첫 번째는 release 운영입니다. Claude Code가 GitHub의 미릴리스 commit을 읽고 release notes를 작성한 뒤 Slack에 짧게 요약합니다. Slack Incoming Webhooks는 빠르게 붙일 수 있지만 메시지 삭제나 복잡한 chat flow에는 맞지 않습니다. 그런 경우 Slack Web API로 넘어갑니다.

두 번째는 billing 자동화입니다. Stripe webhook이 checkout.session.completed를 받고, 고객 정보를 내부 도구에 기록하며, 실패한 작업은 retry queue에 넣습니다. 여기서는 signature verification, idempotency key, test mode와 live mode의 분리가 필수입니다.

세 번째는 support triage입니다. Google Workspace OAuth로 backend가 support CSV를 읽고, Claude Code가 행을 분류한 뒤 GitHub issue를 만들어 engineering follow-up으로 넘깁니다. 사용자 데이터를 다루므로 공유 API 키보다 좁은 read-only OAuth scope가 안전합니다.

네 번째는 audit dashboard입니다. Claude Code가 실행한 모든 SaaS action을 NDJSON으로 남기면 actor, provider, action, target, idempotency key를 팀이 확인할 수 있습니다. 정식 audit platform을 도입하기 전에도 충분히 쓸모가 있습니다.

API 키, OAuth, Webhook, Connector 선택

방식쉬운 설명적합한 경우주의할 점
API 키서버가 가진 공유 credentialStripe 서버 호출, 내부 Slack 알림source code가 아니라 환경 변수나 secret manager에 저장
OAuth사용자나 workspace가 접근 권한을 승인Google Drive, GitHub Apps, 사용자별 작업refresh token과 scope 설계가 중요
WebhookSaaS가 내 endpoint로 이벤트를 전송Stripe 결제, GitHub issue, Slack eventsignature 검증, 중복 delivery, 순서 불일치를 처리
CLI/MCP connectorClaude Code가 호출하는 안정적인 도구runbook, 내부 운영, 여러 SaaS를 잇는 workflowvalidation과 logging을 connector에 넣기

Google OAuth 2.0 문서는 access token과 refresh token 흐름을 설명합니다. GitHub와 Slack에서도 actor를 남기는 로그가 필요합니다. 권한 문제를 분석할 때 “bot이 했다”만으로는 부족합니다.

환경 변수 체크리스트

먼저 .env.example을 만들고 이름만 공유합니다. 실제 값은 Git에 넣지 않습니다.

# .env.example
INTEGRATION_ENV=sandbox
SAAS_API_TOKEN=
SLACK_WEBHOOK_URL=
GITHUB_WEBHOOK_SECRET=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
AUDIT_LOG_PATH=logs/saas-audit.ndjson
# .gitignore
.env
.env.*
!.env.example
logs/

Claude Code가 연동을 실행하기 전에 확인하세요.

  • live key와 test key는 다른 environment에 둔다.
  • secret을 Claude Code prompt에 붙여넣지 않는다.
  • error message에 Authorization header나 webhook secret을 출력하지 않는다.
  • OAuth scope는 read-only에서 시작하고 필요할 때만 넓힌다.
  • CI 값은 일반 repository variable이 아니라 secret으로 등록한다.
  • key rotation 날짜와 담당자를 기록한다.

Retry와 Rate Limit Wrapper

Rate limit은 provider가 허용하는 요청 속도입니다. GitHub REST API에는 primary limit과 secondary limit이 있으며, 제한에 걸리면 response header를 따라야 합니다. Slack은 429 Too Many RequestsRetry-After를 반환합니다. 이 동작은 prompt 지시가 아니라 코드에 있어야 합니다.

// scripts/saas-request.mjs
import crypto from "node:crypto";
import { pathToFileURL } from "node:url";

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

export async function saasRequest(url, options = {}) {
  const {
    method = "GET",
    token = process.env.SAAS_API_TOKEN,
    body,
    idempotencyKey = method === "POST" ? crypto.randomUUID() : undefined,
    maxRetries = 4,
    headers = {},
  } = options;

  for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
    const res = await fetch(url, {
      method,
      headers: {
        Accept: "application/json",
        ...(body ? { "Content-Type": "application/json" } : {}),
        ...(token ? { Authorization: `Bearer ${token}` } : {}),
        ...(idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}),
        ...headers,
      },
      body: body ? JSON.stringify(body) : undefined,
    });

    if (res.ok) return res;

    const retryAfter = Number(res.headers.get("retry-after"));
    const shouldRetry = [408, 409, 425, 429, 500, 502, 503, 504].includes(res.status);

    if (!shouldRetry || attempt === maxRetries) {
      const text = await res.text();
      throw new Error(`SaaS request failed: ${res.status} ${text.slice(0, 200)}`);
    }

    const backoffMs = Number.isFinite(retryAfter)
      ? retryAfter * 1000
      : Math.min(30000, 500 * 2 ** attempt);

    await sleep(backoffMs);
  }

  throw new Error("unreachable");
}

if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
  const url = process.argv[2];
  if (!url) throw new Error("Usage: node scripts/saas-request.mjs <url>");
  const res = await saasRequest(url);
  console.log(await res.text());
}

Stripe처럼 idempotency key를 지원하는 API에서는 같은 POST를 안전하게 재시도할 수 있습니다. 직접 만든 worker도 provider + event_id + action 같은 key를 사용하고 이미 처리된 경우 skip하도록 만드세요.

Webhook 검증 흐름

Webhook은 공개 HTTP entry point입니다. Signature verification이 없으면 누구나 가짜 이벤트를 보낼 수 있습니다. GitHub는 X-Hub-Signature-256, Stripe는 Stripe-Signature를 사용합니다. 핵심은 JSON parsing 전에 raw body를 검증하는 것입니다.

1. Raw body를 읽는다.
2. Signature header를 읽는다.
3. Shared secret으로 HMAC을 계산한다.
4. Timing-safe comparison으로 비교한다.
5. Delivery id를 idempotency key로 저장한다.
6. 빠르게 202를 반환하고 무거운 작업은 queue에서 처리한다.
// scripts/verify-github-webhook.mjs
import crypto from "node:crypto";
import http from "node:http";

const secret = process.env.GITHUB_WEBHOOK_SECRET;
if (!secret) throw new Error("Set GITHUB_WEBHOOK_SECRET");

function verifyGitHubSignature(rawBody, signatureHeader) {
  const received = Array.isArray(signatureHeader)
    ? signatureHeader[0]
    : signatureHeader ?? "";
  const expected =
    "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  const receivedBytes = Buffer.from(received);
  const expectedBytes = Buffer.from(expected);

  return (
    receivedBytes.length === expectedBytes.length &&
    crypto.timingSafeEqual(receivedBytes, expectedBytes)
  );
}

http
  .createServer(async (req, res) => {
    const chunks = [];
    for await (const chunk of req) chunks.push(chunk);
    const rawBody = Buffer.concat(chunks);

    if (!verifyGitHubSignature(rawBody, req.headers["x-hub-signature-256"])) {
      res.writeHead(401);
      res.end("invalid signature");
      return;
    }

    const event = req.headers["x-github-event"];
    const delivery = req.headers["x-github-delivery"];
    console.log(JSON.stringify({ event, delivery, receivedAt: new Date().toISOString() }));

    res.writeHead(202, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ ok: true }));
  })
  .listen(3000, () => console.log("Listening on http://localhost:3000"));

Stripe는 production에서 공식 SDK의 constructEvent()를 사용하는 편이 안전합니다. test webhook secret과 live webhook secret은 다르므로 STRIPE_WEBHOOK_SECRET은 environment별로 관리하세요.

Audit Log

Audit log는 자동화된 action을 나중에 설명하기 위한 기록입니다. Claude Code chat history만으로는 부족합니다. timestamp, actor, provider, action, target, idempotency key, status를 저장하세요.

{
  "ts": "2026-06-03T09:15:00.000Z",
  "actor": "claude-code",
  "provider": "github",
  "action": "create_issue",
  "target": "owner/repo#123",
  "idempotencyKey": "github:issue:customer-42:2026-06-03",
  "status": "succeeded"
}
// scripts/audit-log.mjs
import { appendFile, mkdir } from "node:fs/promises";
import { dirname } from "node:path";

export async function writeAudit(event) {
  const record = {
    ts: new Date().toISOString(),
    actor: event.actor ?? "claude-code",
    provider: event.provider,
    action: event.action,
    target: event.target,
    idempotencyKey: event.idempotencyKey,
    status: event.status ?? "started",
  };
  const file = process.env.AUDIT_LOG_PATH ?? "logs/saas-audit.ndjson";
  await mkdir(dirname(file), { recursive: true });
  await appendFile(file, `${JSON.stringify(record)}\n`, "utf8");
}

if (process.argv[1]?.endsWith("audit-log.mjs")) {
  await writeAudit({
    provider: "slack",
    action: "post_message",
    target: "#release",
    idempotencyKey: "demo-2026-06-03",
    status: "succeeded",
  });
}

첫 버전은 NDJSON이면 충분합니다. 나중에 BigQuery, DuckDB, spreadsheet로 가져올 수 있습니다.

흔한 실수

첫 번째 실수는 webhook을 localhost로 직접 받으려는 것입니다. GitHub troubleshooting 문서에서도 webhook URL은 공개적으로 접근 가능해야 한다고 설명합니다. local test에는 forwarding service를, production에는 HTTPS endpoint를 사용하세요.

두 번째 실수는 webhook 순서를 믿는 것입니다. Provider는 event를 늦게 보내거나 다른 순서로 보낼 수 있습니다. 도착 순서 대신 event timestamp, delivery id, 현재 resource state를 사용하세요.

세 번째 문제는 retry로 중복 작업이 생기는 것입니다. 결제, issue, Slack post, email은 사용자에게 보입니다. POST에는 idempotency key를 붙이고 worker에는 처리된 delivery id를 저장하세요.

네 번째 실수는 OAuth scope를 너무 넓게 잡는 것입니다. read-only, 특정 folder나 workspace, 짧은 token lifetime에서 시작하세요. 위험을 설명할 수 있을 때만 넓힙니다.

다섯 번째 실수는 secret을 Claude Code에 붙여넣는 것입니다. Claude Code에는 environment variable name을 쓰게 하고, 실제 값은 secret manager, CI secret, local .env에 둡니다.

Connector 추상화를 만들 시점

첫 script는 직접적이고 단순해도 됩니다. authorization, pagination, rate limiting, audit logging, error formatting이 세 개 workflow에서 반복되면 connector를 만드세요.

추상화 이름은 업무 언어로 짓습니다. sendMessage(), createTicket(), recordPaymentEvent(), writeAudit()처럼 결과를 드러내는 이름이 좋습니다. 얇은 callSlackApi() wrapper보다 Claude Code가 의도를 이해하기 쉽습니다.

관련 구현은 Claude Code API Design AssistantClaude Code API Testing을 함께 보세요. production access 전에는 Claude Code Security Best Practices로 risk review를 해두는 편이 좋습니다.

공식 참고 문서

Claude Code 연동을 팀 workflow에 넣는다면 첫 incident 전에 재사용 가능한 template을 준비하세요. 실무 checklist는 /products/에, 팀 도입 지원은 /training/에 정리되어 있습니다.

실제로 테스트해 보니 signature verification, idempotency, audit log, 분리된 test environment를 처음부터 넣은 연동이 훨씬 쉽게 확장되었습니다. 작은 Slack 알림도 실패를 추적하고 의도적으로 replay할 수 있을 때 더 안정적입니다.

#claude-code #saas #notion #slack #linear #github #figma #integration
무료

무료 PDF: Claude Code 치트시트

이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.

개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.

Masa

작성자 소개

Masa

Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.