문서 목차

문서/연동 예제

Node.js와 Python

Node.js·Python 서버에서 인증을 생성하고 조회하는 예제

Markdown

Node.js#

Node.js 22.12 이상에서 외부 패키지 없이 실행합니다.

sendwich-client.mjs 다운로드

javascript
// Node.js 22.12+. Server-side only. No dependencies.
const API = 'https://api.sendwich.kr';

export class SendwichError extends Error {
  constructor(status, code, requestId) {
    super(`sendwich: ${code} (${status})`);
    this.status = status;
    this.code = code;
    this.requestId = requestId;
  }
}

async function request(path, method = 'GET', body, idempotencyKey) {
  const key = process.env.SENDWICH_API_KEY;
  if (!key) throw new Error('Set the server-only SENDWICH_API_KEY variable.');
  const response = await fetch(API + path, {
    method,
    signal: AbortSignal.timeout(10000),
    headers: {
      Authorization: `Bearer ${key}`,
      ...(body ? { 'Content-Type': 'application/json' } : {}),
      ...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {})
    },
    ...(body ? { body: JSON.stringify(body) } : {})
  });
  const data = await response.json().catch(() => ({}));
  if (!response.ok) throw new SendwichError(
    response.status, data.error?.code || 'request_failed',
    data.error?.request_id || response.headers.get('X-Request-ID')
  );
  return data;
}

function sessionPath(id) {
  if (!/^vrf_[A-Za-z0-9_-]+$/.test(id)) throw new Error('Invalid verification ID.');
  return `/v1/verifications/${encodeURIComponent(id)}`;
}

export function createVerification(body, idempotencyKey) {
  if (!/^[A-Za-z0-9_.:-]{8,128}$/.test(idempotencyKey || '')) {
    throw new Error('Provide a stable Idempotency-Key for this attempt.');
  }
  // Retry uncertain creates using the same body and key; do not auto-generate here.
  return request('/v1/verifications', 'POST', body, idempotencyKey);
}
export const getVerification = id => request(sessionPath(id));
export const cancelVerification = id => request(sessionPath(id) + '/cancel', 'POST');

같은 디렉터리에 start.mjs를 작성합니다.

javascript
import { randomUUID } from 'node:crypto';
import { createVerification, getVerification } from './sendwich-client.mjs';

const attempt = randomUUID();
const request = { mode: 'discover', client_reference: attempt, state: randomUUID() };
const session = await createVerification(request, attempt);
// 본인의 비공개 터미널에서 확인하는 실행 예제예요.
console.log('인증 ID:', session.id);
console.log('받는 번호:', session.destination);
console.log('보낼 문자:', session.sms_text);
console.log('만료:', session.expires_at);
// 이후 문자를 보낸 뒤 getVerification(session.id)로 조회해요.
bash
SENDWICH_API_KEY='YOUR_LIVE_SECRET_KEY' node start.mjs

Python#

Python 3.10 이상에서 표준 라이브러리로 실행합니다.

sendwich_client.py 다운로드

python
"""Python 3.10+. Standard library only; use on your server."""
import json
import os
import re
from urllib.error import HTTPError
from urllib.request import Request, urlopen

API = "https://api.sendwich.kr"


class SendwichError(Exception):
    def __init__(self, status, code, request_id):
        super().__init__(f"sendwich: {code} ({status})")
        self.status, self.code, self.request_id = status, code, request_id


def _request(path, method="GET", body=None, idempotency_key=None):
    key = os.environ["SENDWICH_API_KEY"]
    headers = {"Authorization": f"Bearer {key}"}
    if body is not None:
        headers["Content-Type"] = "application/json"
    if idempotency_key:
        headers["Idempotency-Key"] = idempotency_key
    data = json.dumps(body).encode("utf-8") if body is not None else None
    try:
        with urlopen(Request(API + path, data=data, headers=headers, method=method), timeout=10) as response:
            return json.load(response)
    except HTTPError as error:
        with error:
            try:
                detail = json.load(error).get("error", {})
            except (ValueError, AttributeError):
                detail = {}
        raise SendwichError(error.code, detail.get("code", "request_failed"),
                            detail.get("request_id") or error.headers.get("X-Request-ID")) from None


def create_verification(body, idempotency_key):
    if not re.fullmatch(r"[A-Za-z0-9_.:-]{8,128}", idempotency_key):
        raise ValueError("Provide a stable Idempotency-Key for this attempt")
    return _request("/v1/verifications", "POST", body, idempotency_key)


def _session_path(verification_id):
    if not re.fullmatch(r"vrf_[A-Za-z0-9_-]+", verification_id):
        raise ValueError("Invalid verification ID")
    return "/v1/verifications/" + verification_id


def get_verification(verification_id):
    return _request(_session_path(verification_id))


def cancel_verification(verification_id):
    return _request(_session_path(verification_id) + "/cancel", "POST")

같은 디렉터리에서 다음과 같이 사용합니다. 실행 환경에 SENDWICH_API_KEY를 설정해야 합니다.

python
import uuid
from sendwich_client import create_verification, get_verification

attempt = str(uuid.uuid4())
session = create_verification(
    {"mode": "discover", "client_reference": attempt, "state": str(uuid.uuid4())},
    attempt,
)
# 본인의 비공개 터미널에서만 코드를 확인해요.
print(session["id"], session["destination"], session["sms_text"], session["expires_at"])
# 문자를 보낸 뒤 get_verification(session["id"])로 조회해요.

서비스에 적용#

인증 ID, 요청 본문, Idempotency-Key를 사용자·시도와 함께 저장합니다. 생성 요청을 재시도할 때는 같은 키와 본문을 사용하고, 오류의 codestatus에 따라 재시도 조건을 적용합니다.

문자 전송 후 getVerification 또는 get_verification으로 조회합니다. 결과 반영 시 확인할 필드는 상태와 결과에 정리되어 있습니다.

예제의 콘솔 출력은 로컬 실행용입니다. 운영 환경에서는 인증 안내를 해당 사용자에게 전달합니다.