MoltCarlo 연동 가이드 (B2B)

귀사(operator) 사이트에 MoltCarlo를 iframe으로 넣고, 귀사 사용자의 잔고로 예측 베팅을 할 수 있게 연동하는 방법입니다. 핵심 모델은 Seamless Wallet + 지연 정산입니다. 본 문서는 개발자용 안내이며, 실제 연동 키·엔드포인트는 온보딩 시 발급됩니다.

한 줄 요약 — 잔고는 항상 귀사가 보관합니다. 사용자가 베팅하면 그 순간 귀사 지갑에서 즉시 차감(debit)되고, 예측 결과가 확정되면(몇 시간~며칠 뒤, iframe이 닫혀 있어도) 서버 간 통신으로 적립(credit)됩니다.

용어 정의 — 먼저 읽어주세요

operator (귀사)
MoltCarlo를 자사 사이트에 iframe으로 임베드하는 파트너 사이트. API의 operator_id가 가리키는 주체입니다. 이 문서에서 ‘귀사’는 곧 operator를 뜻합니다.
MoltCarlo
iframe 안에서 예측 베팅을 제공하는 우리 서비스.
위젯 (widget)
operator 페이지에 임베드되는 MoltCarlo iframe — 실제 거래 화면.
사용자 (end user)
operator의 고객. operator가 인증하며, MoltCarlo는 별도 로그인을 받지 않습니다.
operator 지갑 (wallet)
사용자 잔고가 보관되는 곳. 잔고는 항상 operator가 기준(source of truth)이고, MoltCarlo는 잔고를 보관하지 않습니다.
베팅 원장 (bet ledger)
MoltCarlo가 보관하는 베팅·정산 기록입니다(잔고가 아님).

1. 모델 한눈에 — 왜 ‘지연 정산’인가

카지노 게임(Evolution, Pragmatic 등)은 보통 진입 시 operator 지갑을 잠금(lock) → 플레이 → 종료 시 정산 → 잠금 해제하는 방식입니다. 라운드가 사용자가 보는 앞에서 끝나기 때문입니다.

하지만 예측 베팅은 결과가 한참 뒤에 나옵니다. 사용자가 iframe을 닫고 며칠 뒤에 확정될 수 있습니다. 그래서 ‘종료 시 정산’ 모델은 맞지 않고, 스포츠북(고정배당)과 같은 지연 정산(Seamless Wallet)을 사용합니다:

  • 잔고는 항상 귀사 데이터가 기준(source of truth)입니다. MoltCarlo는 사용자 잔고를 보관하지 않고 베팅 원장만 갖습니다.
  • 베팅 시점에 귀사 지갑에서 즉시 차감(debit) — 홀드(lock)가 아니라 실제 차감입니다.
  • 결과 확정 시점에 당첨금을 적립(credit) — 세션·iframe과 무관하게 서버 간 통신으로, 원래 베팅을 refTxUuid로 참조합니다.

2. 직접 체험 — 플로우 시뮬레이터

아래에서 operator(귀사) 입장의 돈 흐름을 단계별로 눌러보세요. 베팅 시 차감, iframe 닫힘, 며칠 뒤 결과 확정 시 적립까지 — 지갑 호출 로그로 확인할 수 있습니다.

🏦 operator 사이트 (귀사·예시)잔고 1,000,000원
여기에 MoltCarlo iframe이 들어갑니다

실제 위젯도 바로 실행해 보세요:

실제 launch → 세션 → 베팅(데모 지갑 차감) → 결과 확정 시 정산이 일어나는 라이브 샌드박스입니다(데모 잔고 ₩1,000,000으로 시작).

3. 연동 4단계

  1. 온보딩 — MoltCarlo가 귀사에 operator_id,api_key, 서명 키(운영은 비대칭 RSA/Ed25519, 샌드박스는 HMAC), 그리고 귀사의 walletUrl·허용 IP·임베드 출처(allowedOrigins)·베팅 한도(스테이크)를 등록합니다.
  2. Launch — 사용자가 위젯을 열 때, 귀사 백엔드가 서명된 요청을 보내 1회용 위젯 URL을 받고 그 URL을 iframesrc로 사용합니다. (4·5절)
  3. Wallet 콜백 구현 — 귀사가 balance·bet(debit)·win(credit)·rollback 네 개의 콜백을 구현합니다. MoltCarlo가 베팅·정산 때 이걸 호출합니다. (6절)
  4. 정산 — 결과가 확정되면 MoltCarlo가 win을 호출해 적립합니다(refTxUuid로 원래 베팅 참조). 사용자가 접속 중이 아니어도 됩니다.

4. Launch 핸드셰이크

귀사 백엔드 → MoltCarlo. 요청 본문을 귀사 서명 키로 서명하고 X-MC-Signature 헤더에 담습니다. 본문에는 재전송 공격(replay) 방지를 위해 timestampnonce를 포함합니다.

요청 본문

필드타입설명
operator_idstring발급받은 operator 식별자
userstring귀사 사용자의 고유 ID (외부 ID로 매핑됨)
tokenstring귀사의 사용자 세션 토큰 — MoltCarlo가 지갑 호출 시 되돌려줌
currencystring예: KRW (샌드박스 데모는 "XXX")
langstring?ko / en / ja (기본 ko)
timestampnumber유닉스 ms, 5분 skew 초과 시 거절
noncestring1회성 임의값 — 재전송 공격(replay) 방지

응답

200 OK
{ "url": "https://moltcarlo.com/widget#lt=<1회용-토큰-120초>" }

url을 iframe src로 넣으면, 위젯이 1회용 lt를 세션 토큰으로 교환하고(원자적으로 한 번만 사용) 거래 화면이 뜹니다. 세션 토큰은 쿠키가 아니라 헤더(X-MC-Session) 로 전달되어 3rd-party 쿠키 차단 환경에서도 동작합니다.

5. 코드 샘플 — Launch 요청 (귀사 백엔드)

Node.js — 위젯 URL 받기
import crypto from "crypto";

const OPERATOR_ID = "op_yoursite";
const SECRET = process.env.MC_SECRET;            // 온보딩 시 발급 (샌드박스: HMAC)
const MC_BASE  = "https://moltcarlo.com";

async function getWidgetUrl(user, userToken) {
  const body = {
    operator_id: OPERATOR_ID,
    user,                                        // 귀사 사용자 ID
    token: userToken,                            // 귀사 사용자 세션 토큰
    currency: "KRW",
    lang: "ko",
    timestamp: Date.now(),
    nonce: crypto.randomUUID(),
  };
  const payload = JSON.stringify(body);
  const sig = crypto.createHmac("sha256", SECRET).update(payload).digest("hex");

  const res = await fetch(MC_BASE + "/api/op/v1/launch", {
    method: "POST",
    headers: { "content-type": "application/json", "X-MC-Signature": sig },
    body: payload,
  });
  const { url } = await res.json();
  return url;                                    // <iframe src={url}> 로 사용
}

⚠️ url(lt 포함)은 1회용·120초입니다. 토큰은 URL 프래그먼트(#)에 담겨 Referer 헤더·서버 접근 로그에 남지 않습니다.

6. Wallet 콜백 — 귀사가 구현 (잔고 기준 데이터)

MoltCarlo → 귀사 walletUrl. 네 가지 콜백을 구현합니다. 모든 호출은 서명되어 오고(검증 필수), transaction_uuid 멱등키로 사용합니다. 금액은 정수 minor unit(1원 = 1, 소수점 금지)입니다.

필드타입설명
POST /balance조회→ { balance } (현재 잔고)
POST /bet차감(debit)베팅 스테이크 차감. 잔고 부족 시 RS_ERROR_NOT_ENOUGH_MONEY
POST /win적립(credit)정산/매도 당첨금 적립. reference_transaction_uuid = 원래 bet
POST /rollback되돌림차감 실패/취소 시 원래 bet을 되돌림. reference_transaction_uuid 참조

공통 요청/응답

필드타입설명
operator_idstring귀사 식별자 (서명·api_key와 일치해야 함)
userstring귀사 사용자 ID
tokenstringLaunch 때 넘긴 귀사 사용자 토큰 (bet/balance에서 사용)
transaction_uuidstring이 호출의 멱등키 (재시도 시 같은 결과 반환)
reference_transaction_uuidstring?win/rollback이 참조하는 원래 bet의 uuid
roundstring라운드 ID — 베팅이 속한 마켓 단위 식별자
amountintegerminor unit (1원=1)
currencystringKRW

응답은 { status: "RS_OK", balance, request_uuid } 형태로, 받은 request_uuid를 그대로 echo 합니다. 상태값:

  • RS_OK · 정상
  • RS_ERROR_NOT_ENOUGH_MONEY · 잔고 부족
  • RS_ERROR_DUPLICATE_TRANSACTION · 멱등 중복
  • RS_ERROR_INVALID_SIGNATURE · 서명 오류
Node.js (Express) — 지갑 콜백 핸들러 (요지)
app.post("/wallet/:op", async (req, res) => {
  // 1) 서명 검증 — 실패 시 RS_ERROR_INVALID_SIGNATURE
  if (!verify(req.rawBody, req.header("X-MC-Signature"))) {
    return res.json({ status: "RS_ERROR_INVALID_SIGNATURE" });
  }
  const b = req.body;
  // 2) operator_id / api_key / 서명키가 모두 같은 operator인지 확인
  // 3) 멱등: 같은 transaction_uuid를 이미 처리했으면 '그때 결과'를 반환
  const prev = await findTxn(b.transaction_uuid);
  if (prev) return res.json(prev.result);        // 절대 중복 반영 금지

  if (req.path.endsWith("/bet")) {               // 차감
    const bal = await getBalance(b.user);
    if (bal < b.amount)
      return res.json({ status: "RS_ERROR_NOT_ENOUGH_MONEY", balance: bal });
    const after = await debit(b.user, b.amount, b.transaction_uuid, b.round);
    return res.json(save(b, { status: "RS_OK", balance: after }));
  }
  if (req.path.endsWith("/win")) {               // 적립 (정산)
    // ✅ 반드시: reference_transaction_uuid에 해당하는 '직전 bet'이 존재하고
    //    서명이 유효할 때만 적립. (user, refTxUuid)만으로 적립하면 위조 위험!
    const after = await credit(b.user, b.amount, b.transaction_uuid,
                               b.reference_transaction_uuid);
    return res.json(save(b, { status: "RS_OK", balance: after }));
  }
  // rollback 도 동일 패턴: refTxUuid의 bet을 되돌림, 멱등.
});

7. 보안 체크리스트 (꼭 지키세요)

  • 서명 검증 — 모든 인바운드 콜백의 서명을 검증. 운영은 비대칭 키(키가 유출돼도 상대 호출 위조 불가).
  • 멱등(Idempotency)transaction_uuid로 중복 처리 방지. 재시도 시 그때의 결과를 반환(새로 반영 금지). 멱등 검증은 (uuid, type, amount)까지 확인.
  • 적립은 직전 debit 매칭 후에만 winreference_transaction_uuid의 bet이 실제로 존재하고 서명이 유효할 때만 적립. (user, refTxUuid)만으로 승인하면 정산 위조에 노출됩니다.
  • 금액은 정수 minor unit — float 금지, 음수·과대값 가드.
  • 출처·IP 고정 — 임베드 출처(allowedOrigins)· 허용 IP는 온보딩 시 등록된 값만. postMessage는 등록 출처 로만.
  • 잔고 변경은 클라이언트가 할 수 없음 — 모든 차감/적립은 서버 간(S2S) 통신입니다. 위젯(브라우저)이 보내는 값으로 잔고가 바뀌지 않습니다.

8. operator 관리 페이지 & 인증

네, operator 전용 관리 포털이 있습니다 → /op-portal. 샌드박스는 operator_id + api_key로 로그인합니다 (데모: op_demo / op_demo_key). 로그인하면 잔고 노출·미결 베팅·정산 상태·연동 키·한도를 확인할 수 있습니다.

  • 지금 제공: 미결 노출·베팅액, 상태별 베팅, 최근 베팅 내역, 연동 키(마스킹)·walletUrl·한도 조회, 지급 실패(STRANDED) 경보
  • 준비 중: 키 회전(rotate), 대사(정산 대조)용 CSV, 콘텐츠 opt-out, 지급 실패 건 재처리, 샌드박스/라이브 전환

인증은 3계층입니다

필드타입설명
① 머신 (귀사 백엔드 ↔ MoltCarlo)api_key + 서명 + IP + noncelaunch·지갑 콜백의 서버 간(S2S) 인증
② 포털 (귀사 직원 ↔ /op-portal)operator_id + api_key (샌드박스); 운영은 직원 로그인+2FAoperatorId로 스코프된 백오피스
③ 엔드 사용자 (귀사 사용자 ↔ iframe)launch 토큰별도 로그인 없음 — 귀사가 인증한 사용자의 신원을 서명 토큰으로 위임

즉, 귀사 사용자는 MoltCarlo에 가입·로그인하지 않습니다. 귀사가 자체 인증한 사용자를 launch 토큰으로 넘기면, MoltCarlo는 서명을 검증하고 (operatorId, 사용자ID)로 매핑합니다.

9. 샌드박스 → 라이브

MoltCarlo가 레퍼런스 지갑(샌드박스)을 호스팅하므로, 귀사 지갑 구현 없이도 전체 루프(launch → 베팅 차감 → 며칠 뒤 정산 적립 → operator 잠깐 다운 시 재시도)를 먼저 검증할 수 있습니다. 인증 체크리스트 (특히 ‘세션 없이 refTxUuid로 정산’, 멱등, 서명)를 통과하면 라이브로 전환합니다.

연동 문의: contact@moltcarlo.com

본 문서는 개발자 안내용이며 사전 고지 없이 갱신될 수 있습니다. (초안 2026-06-28)