Community코딩 & 개발github.com

Nomba 웹훅

서명을 검증하고, 중복 전달을 제거하며, 각 이벤트(payment_success, payment_failed, payment_reversal, payout_success, payout_failed, payout_refund)에 대해 올바르게 동작하는 안전한 Nomba 웹훅 엔드포인트를 구축합니다. 사용자가 Express, Next.js, NestJS, Fastify, Flask, FastAPI, Django, Laravel, Symfony 또는 일반 PHP에서 Nomba 웹훅 또는 '알림'/'콜백'을 수신, 처리, 확인 또는 디버깅하려고 할 때, 또는 단순히 '내 서버에서 결제를 확인하라', '성공적인 이체를 수신하라'고 말하거나 event_type과 nomba-signature 헤더가 포함된 페이로드를 붙여넣을 때, 또는 기존 웹훅 핸들러를 검토할 때 사용하세요.

Nomba 웹훅란 무엇인가요?

이 스킬은 Nomba 결제 게이트웨이로부터 안전하고 신뢰할 수 있는 웹훅 이벤트를 처리하는 방법을 설명합니다. `verifyWebhook`(Node.js), `verify_signature`(Python), 또는 `Webhook::verify`(PHP)와 같은 공식 SDK 검증 기능을 사용하여 HMAC 서명을 검증하는 방법을 단계별로 안내합니다. 핵심 기능으로는 `requestId`를 기반으로 한 중복 제거, 예기치 않은 이벤트 처리, `payout_failed`가 아닌 `payout_refund`에서만 환불을 진행하여 이중 결제를 방지하는 방법을 다룹니다. 또한 서명이 결제 금액을 보장하지 않기 때문에 주문을 처리하기 전에 추가 API 호출로 금액과 통화를 확인하는 것이 중요함을 강조합니다. 스킬은 Express/Node.js 예제와 다양한 프레임워크(Flask, FastAPI, Django, Laravel 등)에 대한 참조, SDK 테스트 벡터를 사용한 단위 테스트 및 엔드투엔드 샌드박스 테스트, 재생 공격 방어를 위한 타임스탬프 허용 오차 설정에 대한 지침을 포함합니다. 실패한 경우 적절한 HTTP 상태 코드(401/400)를 반환하고, 느린 작업은 핸들러 외부의 백그라운드 잡으로 오프로드하며, 웹훅 경로를 CSRF 보호에서 제외하고 HTTPS를 사용하는 등의 보안 관행을 강조합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/NombaOSS/skills/tree/HEAD/skills/nomba-webhooks

Installed? Explore more 코딩 & 개발 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

Nomba 웹훅은(는) 무엇을 하나요?

웹훅은 Nomba가 서버에 실제로 자금이 이동했음을 알리는 방법입니다. 인터넷상의 누구나 웹훅 URL에 POST할 수 있으므로, 핸들러는 주문이 결제되었거나 환불되었다고 표시하기 전에 각 요청이 Nomba에서 왔음을 증명해야 합니다. 또한 Nomba가 동일한 이벤트를 재시도하는 상황도 처리해야 합니다.

HMAC을 직접 구현하지 말고 공식 SDK의 검증기를 사용하세요. Nomba의 서명은 :로 연결된 특정 필드를 포함하며, SDK는 단순한 구현이 놓칠 수 있는 재생 공격도 차단합니다.

언어검증 방법
Node.js / TS (@nombaoss/nomba-node)verifyWebhook(rawBody, headers, signingKey)
Python (nomba)from nomba.webhooks import verify_signature → verify_signature(raw_body, headers, signing_key)
PHP (nombaoss/nomba-php)Nomba\Webhook::verify($rawBody, $headers, $signingKey)

각 언어의 정확한 임포트와 인수 타입은 references/sdk-setup.md에 나와 있습니다. 각 함수는 파싱된 이벤트를 반환하거나 예외(WebhookVerificationError / WebhookVerificationException)를 던집니다. 이 함수들은 API 자격 증명이 필요하지 않으며, Nomba 대시보드(개발자 → 웹훅 설정)의 웹훅 서명 키(signing key) 만 필요합니다. 환경 변수(예: NOMBA_WEBHOOK_SIGNING_KEY)에서 읽어오세요. 이는 API 클라이언트 시크릿과는 다른 비밀 키입니다.

핸들러, 단계별로

  1. 원본 바디를 가져옵니다. Nomba가 보낸 정확한 바이트를 검증하세요. JSON 바디 파서가 먼저 실행되고 객체를 다시 직렬화하면 서명이 일치하지 않을 수 있으며, 더 나쁜 경우 실제로 처리하는 것과 다른 내용을 검증하게 될 수 있습니다. 각 프레임워크마다 원본 바디를 가져오는 방법이 다릅니다. 올바른 방법은 references/frameworks.md를 참조하고, 해당 경로를 CSRF 보호에서 제외하세요(프레임워크에 해당 기능이 있는 경우).
  2. 검증 후 파싱합니다. 검증기가 반환한 이벤트를 사용하세요. 실패 시 401(또는 400)을 응답하고 중단하세요. info 레벨에서 거부된 요청의 전체 바디나 헤더를 로깅하지 마세요.
  3. requestId로 중복을 제거합니다. Nomba는 전달을 재시도하므로, 동일한 이벤트가 두 번 이상 도착할 수 있습니다. 처리된 각 requestId를 고유 제약 조건과 함께 기록하고, 이미 본 requestId는 건너뛰세요. 중복 요청에도 200을 반환해야 합니다. 그렇지 않으면 Nomba가 계속 재시도합니다.
  4. 빠르게 응답을 확인합니다. 즉시 200을 반환하고, 느린 작업(이메일 발송, 주문 처리, 원장 업데이트 등)은 requestId를 키로 하는 백그라운드 잡에서 처리하세요. 느린 핸들러는 타임아웃과 재전송을 유발합니다.
  5. 아래 표를 사용하여 이벤트에 따라 조치합니다. 이벤트를 참조 번호로 자신의 레코드와 매칭하고, 금액과 통화가 예상과 일치하는지 확인한 후 주문을 처리하세요.

각 이벤트의 의미

event_type매칭 기준조치
payment_success체크아웃: data.order.orderReference; 가상 계좌 / 이체 입금: data.transaction.aliasAccountReference 또는 transactionIddata.transaction.transactionAmount(및 통화)를 주문과 확인한 후 결제 완료로 표시합니다. 토큰화된 체크아웃의 경우 data.tokenizedCardData.tokenKey(존재하고 "N/A"가 아닌 경우)를 고객과 함께 서버 측에만 저장합니다.
payment_failed위와 동일시도 실패로 표시하고, 고객이 다시 시도하도록 합니다. 요금이 청구되지 않았습니다.
payment_reversal위와 동일받은 자금이 취소되었습니다. 주문 처리를 취소하거나 검토를 위해 플래그를 지정합니다.
payout_successdata.transaction.merchantTxRef이체 또는 청구서 결제가 완료되었음을 표시합니다.
payout_faileddata.transaction.merchantTxRef아직 환불하지 마세요. Nomba가 다른 경로를 통해 재시도할 수 있습니다. 실패로 표시하고 기다리세요.
payout_refunddata.transaction.merchantTxRef자금이 Nomba 지갑으로 반환되었습니다. 이제 환불하거나 고객에게 크레딧을 제공하고 지급 실패로 표시해도 안전합니다.

payout_failed(또는 타임아웃 시)에 환불하는 대신 payout_refund에서 환불해야 상인이 고객에게 두 번 지불하는 고전적인 실수를 방지할 수 있습니다. 인식할 수 없는 이벤트의 경우 로그를 남기고 200을 반환하세요.

API로 금액 결정을 확인하세요. Nomba의 서명은 event_type, requestId, data.merchant.userId, data.merchant.walletId, data.transaction.transactionId, type, time, responseCode 및 타임스탬프만 포함합니다. 금액, orderReference, merchantTxRef 및 카드 tokenKey는 서명되지 않습니다. 따라서 주문 처리, 환불 또는 카드 토큰 저장 전에 SDK를 사용하여 재조회하고 API의 응답에 따라 조치하세요: 체크아웃 결제의 경우 transactions.get({ orderReference }) (프로덕션에서는 checkout.getTransaction도 사용 가능), 지급의 경우 transactions.get({ merchantTxRef }). 서명된 requestId로 중복을 제거하면 이미 처리한 이벤트의 변조된 복사본을 방지하고, 재조회가 나머지를 보장합니다.

재생 보호

requestId로 중복을 제거하는 것이 주요 방어책입니다: 이미 처리한 이벤트의 재전송 또는 재시도 전달은 아무것도 변경하지 않습니다.

검증기는 오래된 타임스탬프를 거부할 수도 있지만(toleranceSeconds / tolerance_seconds / PHP의 네 번째 인수), 기본적으로는 비활성화하는 것이 좋습니다. Nomba는 nomba-timestamp를 페이로드가 전송된 시간으로 문서화하지만, 자동 재시도 시 새 타임스탬프가 생성되는지는 명시하지 않습니다. 만약 원래 타임스탬프가 유지된다면, 허용 오차가 Nomba의 재시도 기간보다 짧으면 장애 후 합법적인 재시도를 거부하여 유료 주문이 확인되지 않을 수 있습니다. 샌드박스에서 재전달 시 새 타임스탬프가 전달되는지 확인한 후에만 활성화하고, 서버 시계가 동기화된 상태에서 충분히 여유 있게(예: 1시간) 설정하세요.

예제 (Node.js + Express)

import express from "express";
import { verifyWebhook, WebhookEventType } from "@nombaoss/nomba-node";

const app = express();

// 이 경로에 대해서만 원시 바디 사용; 앱 전체 express.json()보다 먼저 등록하세요.
app.post("/webhooks/nomba", express.raw({ type: "*/*" }), async (req, res) => {
  let event;
  try {
    event = verifyWebhook(req.body, req.headers, process.env.NOMBA_WEBHOOK_SIGNING_KEY!);
  } catch {
    return res.sendStatus(401);
  }

  // request_id에 대한 고유 제약 조건; 이미 본 경우 false 반환
  const isNew = await webhookEvents.recordOnce(event.requestId, event.event_type);
  if (isNew) await jobs.enqueue("nomba-webhook", event); // 백그라운드에서 처리
  res.sendStatus(200);
});

Python (Flask/FastAPI/Django), PHP (Laravel/일반) 및 기타 Node 프레임워크는 references/frameworks.md에 있습니다.

테스트

  • 단위 테스트: SDK 문서화된 테스트 벡터를 사용하여 핸들러를 테스트하세요: SDK의 webhook-fixture.json에서 바디, 키 SampleSignatureKey123, 헤더 nomba-timestamp: 2025-09-29T10:51:44Z, nomba-signature: q30yFZDyykoymBf9+mt1wz5UAuLk57raPGPHlGzIv00=. 잘못된 키, 변조된 바디, 누락된 헤더가 거부되는지, 그리고 중복된 requestId가 한 번만 처리되는지 확인하세요.
  • 종단 간: 대시보드에서 테스트 웹훅 URL과 서명 키를 설정하고, 터널을 사용하여 로컬 서버를 노출시킨 후 샌드박스 결제를 해보세요. 대시보드에서 전달을 다시 푸시하거나 디버깅할 수 있습니다 (https://developer.nomba.com/docs/api-basics/troubleshoot-webhooks).

체크리스트

  • 원시 바디가 비즈니스 로직이나 파싱 전에 SDK로 검증됩니다.
  • 서명 키는 코드가 아닌 환경 변수에서 가져옵니다.
  • 잘못된 서명은 401/400을 반환합니다; 다른 작업은 수행되지 않습니다.
  • requestId는 고유 제약 조건으로 중복 제거됩니다; 중복에도 200을 반환합니다.
  • 무거운 작업은 백그라운드에서 실행됩니다; 핸들러는 빠르게 200을 반환합니다.
  • 주문 처리 전에 금액과 통화를 주문과 비교하여 확인합니다.
  • 환불은 payout_refund(또는 재조회 결과 REFUND 반환)에서만 진행됩니다.
  • 경로는 CSRF 보호에서 제외되고 HTTPS를 사용합니다.

관련 스킬