전체 블로그

// 활용 사례

Slack 에서 자연어로 어제 매출 묻기

슬랙에 "어제 매출?" 한 줄을 던지면 봇이 입금 합계로 답한다. headless 거래내역 API 와 슬랙 봇을 60줄로 잇는 방법.

·headless

월요일 아침마다 누군가 은행 사이트에 들어가 주말 입금을 더하고, 그 숫자를 슬랙에 옮겨 적는다. 그 일을 봇에 맡기면 채널에 어제 매출? 한 줄만 남는다.

이 글은 슬랙 봇 하나를 60줄로 만들어 headless 거래내역 API 에 잇는다. 셋업까지 약 15분, 그다음부터는 질문 한 줄.

어떤 상황을 풀고 있나

  • 매출을 매일 확인하는 사람: 대표, 재무 담당, 이커머스 운영자
  • 빈도: 매일~매주. 정산일이 몰리면 하루에도 여러 번
  • 지금 방식: 은행 사이트 로그인 → 거래내역 다운로드 → 엑셀에서 입금만 필터 → 슬랙에 복사

마지막 단계만 자동화하는 것이 아니라 처음부터 끝까지 봇에게 맡긴다.

결과 — 한 화면

슬랙 채널에서:

@매출봇 어제 매출?

# 잠시 후 …
2026-05-26 입금 18건. 합계 ₩7,420,000.
가장 큰 입금은 ㈜에이클라이언트 ₩2,200,000.

@매출봇 지난주 매출? 처럼 기간을 바꿔 물어도 같은 흐름으로 답한다.

1. 준비물

준비물역할비용
headless API key거래내역 수집무료로 시작
은행 자격증명 1건입금이 찍히는 계좌
Slack 앱 (봇 토큰)메시지 송수신무료
Node 18+ 런타임봇 프로세스로컬·서버 무관

자격증명은 한 번만 등록한다. headless 가 봉투 암호화로 보관하고, 수집 시점에만 메모리에서 복호화한다.

bash
curl -X POST https://api.h6s.ai/api/v1/credentials \
  -H "Authorization: Bearer $H6S_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "providerCode": "CB_KB",
    "authMethod": "ID_PW",
    "credentials": { "loginId": "...", "loginPw": "..." }
  }'
# → { "id": "cred_...", "credentialHealth": "UNKNOWN" }

2. 핵심 코드 — 60줄 이내

슬랙 Bolt 앱 하나가 멘션을 받아 headless 에 거래내역을 요청하고, 입금만 더해 답한다.

javascript
import pkg from "@slack/bolt";
const { App } = pkg;

const API = "https://api.h6s.ai/api/v1";
const headers = {
  Authorization: `Bearer ${process.env.H6S_API_KEY}`,
  "Content-Type": "application/json",
};

// data-job 1건을 만들고 끝날 때까지 폴링한 뒤 결과를 돌려준다
async function fetchTransactions(start, end) {
  const job = await fetch(`${API}/data-jobs`, {
    method: "POST",
    headers,
    body: JSON.stringify({
      credentialId: process.env.H6S_CREDENTIAL_ID,
      schema: "bank.transactions.cb.v1",
      dateRangeStart: start,
      dateRangeEnd: end,
    }),
  }).then((r) => r.json());

  let status = job.status;
  // 예제는 30초 가드만 — 프로덕션은 타임아웃·재시도·에러 핸들링을 더 둔다
  for (let i = 0; (status === "PENDING" || status === "RUNNING") && i < 15; i++) {
    await new Promise((r) => setTimeout(r, 2000));
    const s = await fetch(`${API}/data-jobs/${job.id}`, { headers }).then((r) => r.json());
    status = s.status;
  }
  if (status !== "SUCCESS") throw new Error(`job ${job.id} 미완료: ${status}`);
  return fetch(`${API}/data-jobs/${job.id}/results`, { headers }).then((r) => r.json());
}

const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  signingSecret: process.env.SLACK_SIGNING_SECRET,
});

app.event("app_mention", async ({ event, say }) => {
  const day = /지난주/.test(event.text) ? 7 : 1;
  const end = new Date();
  const start = new Date(Date.now() - day * 86400000);
  const iso = (d) => d.toISOString().slice(0, 10);

  const { records } = await fetchTransactions(iso(start), iso(end));
  const deposits = records.filter((r) => r.amount > 0);
  const total = deposits.reduce((s, r) => s + r.amount, 0);
  const top = deposits.sort((a, b) => b.amount - a.amount)[0];

  await say(
    `${iso(start)}~${iso(end)} 입금 ${deposits.length}건. 합계 ₩${total.toLocaleString()}.\n` +
      (top ? `가장 큰 입금은 ${top.description} ₩${top.amount.toLocaleString()}.` : "입금 없음."),
  );
});

await app.start(3000);

bank.transactions.cb.v1amount 는 입금이 양수, 출금이 음수다. 어느 은행에서 받아도 8개 필드가 같다. 위 필터를 그대로 쓴다.

3. 배포

봇은 멘션을 기다리는 상주 프로세스다. cron 이 아니라 늘 떠 있어야 한다.

  • 가장 단순한 길: 작은 서버나 컨테이너에 node bot.js 상주
  • secret 4개를 환경변수로: H6S_API_KEY, H6S_CREDENTIAL_ID, SLACK_BOT_TOKEN, SLACK_SIGNING_SECRET
  • 첫 확인: 채널에 봇을 초대하고 @매출봇 어제 매출? 한 줄

기본 Bolt 구성에서는 이벤트를 받자마자 200 을 응답하고 핸들러를 비동기로 돌리므로 폴링이 길어도 슬랙이 재시도하지 않지만, ack 를 핸들러 완료 뒤로 미루는 환경(예: AWS Lambda 리시버, processBeforeResponse: true)에서는 이벤트를 먼저 ack 하고 결과를 client.chat.postMessage 로 따로 보내는 편이 안전하다.

4. 운영 — 한 달 뒤 무엇을 보는가

  • 호출량: 하루 한두 번 물으면 월 50건 안쪽. 무료 플랜의 월 수집 요청 한도(20건)를 넘기면 스탠다드로 올린다
  • 자격증명 만료: 응답이 비거나 failureCategory: CREDENTIAL 이 보이면 콘솔에서 자격증명을 갱신한다
  • 타임존: dateRangeStart/End 는 날짜 단위다. "어제" 의 경계가 KST 기준인지 한 번 확인한다

5. 다른 데 붙이기

같은 골격에서 데이터 형식과 집계만 바꾸면 다른 자동화가 된다.

  • 세금계산서 합계 → hometax.tax-invoices.sales.v1
  • 매출 추세를 시트에 누적 → 데이터베이스/스프레드시트로 append
  • 정기 리포트 → 멘션 대신 cron 으로 매일 아침 자동 전송

더 깊이

같은 골격을 다른 채널로 옮기려면 표준 MCP 클라이언트 연동부터 보면 된다. MCP 연동 문서에 설치와 인증이 정리돼 있다.

봇이 한 번 답하기 시작하면, 다음은 묻지 않아도 매일 아침 먼저 알려주는 쪽이다. 멘션 핸들러를 cron 으로 바꾸면 그대로 된다.