보안 정책
API 키 관리, IP ACL 설정, HTTPS 강제, 웹훅 서명 검증 등 비즈고 API를 안전하게 사용하기 위한 보안 정책을 안내합니다.
API 키 보안
- API 키를 코드에 하드코딩하지 마세요. 환경 변수 또는 시크릿 관리 서비스를 사용하세요.
- 키가 유출된 경우 즉시 콘솔에서 재발급하세요.
- 개발용·운영용 API 키를 분리해 관리하세요.
Bash
# 잘못된 예
BIZGO_API_KEY = "your_actual_key_here" # 코드에 직접 삽입 금지
# 올바른 예 (.env 파일 또는 시크릿 매니저)
BIZGO_API_KEY = os.environ.get("BIZGO_API_KEY")
JavaScript
// 잘못된 예
const apiKey = "your_actual_key_here"; // ✗
// 올바른 예
const apiKey = process.env.BIZGO_API_KEY; // ✓
IP ACL
비즈고 API는 등록된 IP에서만 요청을 허용합니다.
- 서버 IP 변경 시 반드시 콘솔에서 ACL을 업데이트하세요.
- 개발용/운영용 API 키를 분리해 각각 다른 IP 범위를 등록하세요.
- CIDR 표기로 IP 대역을 등록할 수 있습니다:
203.0.113.0/24
IP ACL에 등록되지 않은 IP에서 호출하면 인증 또는 접근 제어 단계에서 차단됩니다. 응답의
authCode는 코드표에서 확인하세요.
HTTPS 강제
모든 API 요청은 HTTPS를 사용해야 합니다.
HTTP 요청은 거부됩니다.
웹훅 서명 검증
리포트를 웹훅(WEBHOOK)으로 받으면 요청 헤더의 서명으로 비즈고가 보낸 요청인지 확인할 수 있습니다.
위변조 방지를 위해 반드시 서명을 검증하세요.
수신된 헤더:
TEXT
X-IB-Timestamp: 1743381600000
X-IB-Signature: {Base64(HmacSHA256(secret, X-IB-Timestamp))}
| 헤더 | 설명 |
|---|---|
X-IB-Timestamp | 웹훅을 보낸 시각입니다. Unix epoch 밀리초(ms) 값을 문자열로 보냅니다. |
X-IB-Signature | X-IB-Timestamp 문자열을 웹훅 시크릿으로 HMAC-SHA256 서명한 뒤 Base64로 인코딩한 값입니다. 요청 본문은 서명 대상이 아닙니다. |
- 웹훅 시크릿은 웹훅 검증 전용 값으로, 영업담당자에게 요청해 받습니다. 받은 문자열을 Base64 디코딩하지 않고 그대로 HMAC 키로 씁니다.
- 재전송(replay) 공격을 막으려면
X-IB-Timestamp가 현재 시각과 300초 넘게 차이 나는 요청을 거부하세요. 비즈고 SDK의 기본 허용 오차와 같습니다. - 서명은
crypto.timingSafeEqual같은 상수 시간 비교로 확인하세요.===비교는 타이밍 공격에 노출됩니다. - 방화벽에서는 비즈고 웹훅 발신 IP만 허용하세요.
응답 규격
검증에 성공하면 5초 안에 HTTP 200과 함께 받은 msgKey를 그대로 반환합니다.
JSON
{ "msgKey": "SMS20260331-000001" }
응답이 없거나 응답의 msgKey가 받은 값과 다르면 수신 실패로 보고 최대 3회 다시 보냅니다. OK 같은 문자열만 반환하면 매번 실패로 처리돼, 재전송이 끝나면 그 리포트를 받을 수 없습니다.
검증 예제 (Node.js)
JavaScript
const crypto = require('crypto');
const express = require('express');
const app = express();
const WEBHOOK_SECRET = process.env.BIZGO_WEBHOOK_SECRET;
const TOLERANCE_MS = 300 * 1000; // 허용 오차 300초
function verifySignature(timestamp, signature) {
const sentAt = Number(timestamp); // ms 단위
if (!Number.isFinite(sentAt) || typeof signature !== 'string') return false;
if (Math.abs(Date.now() - sentAt) > TOLERANCE_MS) return false;
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(timestamp)
.digest('base64');
const a = Buffer.from(expected);
const b = Buffer.from(signature);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
app.post('/webhook/report', express.json(), (req, res) => {
const timestamp = req.get('X-IB-Timestamp');
const signature = req.get('X-IB-Signature');
if (!verifySignature(timestamp, signature)) return res.status(401).end();
// 무거운 처리는 비동기로 분리해 5초 안에 응답
setImmediate(() => processReport(req.body));
// 수신한 msgKey를 그대로 반환 (필수)
res.status(200).json({ msgKey: req.body.msgKey });
});
서명은 요청 본문을 보호하지 않습니다. 결제·인증처럼 중요한 판단은 리포트 조회 API로 결과를 다시 확인하세요.
에러 로그 민감정보 마스킹
전화번호, 인증번호 등 민감정보는 로그에 마스킹 처리하세요.
JavaScript
function maskPhone(phone) {
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
}
console.log(`발송 대상: ${maskPhone('01012345678')}`); // 010****5678
다음 단계
- 환경 설정 → 환경 설정하기