# SynapsCore Bridge API — 퀵스타트

> 이 문서는 **외부 개발자**를 위한 튜토리얼이다. 내부 아키텍처 설계 배경이 궁금하면
> Swagger 레퍼런스(`/api/docs/`)를 함께 참고하라 — 이 문서는 "무엇을 왜"가 아니라
> "무엇을 어떻게, 지금 바로"에 집중한다.
>
> 베이스 URL: `https://synapscore.xyz/api`
> 이 문서 자체의 URL: `https://synapscore.xyz/static/docs/api-quickstart.md`

---

## 1. 인증 — 토큰 발급부터 API 키까지

SynapsCore Bridge 연동은 두 단계 인증을 거친다.

1. **프로비저닝 토큰** — 영업팀/담당자에게 연동 신청을 하면 파트너 1명(1개 회사)당 토큰을 하나 발급해준다. 이 토큰으로 딱 한 번, 신규 가맹점을 등록한다.
2. **API 키** — 1단계에서 가맹점을 등록하면 그 응답에 실제 결제 API를 호출할 `api_key`가 함께 발급된다. 이후 모든 결제 관련 호출은 이 키를 쓴다.

### 1-1. 가맹점 등록 (프로비저닝 토큰 사용, 1회)

```bash
curl -X POST https://synapscore.xyz/api/v1/merchants/provision/ \
  -H "X-Provisioning-Key: <발급받은 토큰>" \
  -H "Content-Type: application/json" \
  -d '{
    "external_ref": "your-service:your-shop-id",
    "name": "My Shop",
    "service_url": "https://your-service.example.com",
    "mode": "test",
    "settlement_address": "0x1111111111111111111111111111111111111111"
  }'
```

- `mode`를 `"test"`로 주면 **실제 자금 이동 없이** 전체 플로우를 검증할 수 있는 샌드박스 가맹점이 만들어진다(§4 참고). 실서비스 전환 시에는 `"live"`로 새로 등록하거나 담당자에게 전환을 요청하라.
- `settlement_address`는 정산받을 지갑 주소다(EVM 형식). Tron으로 정산받으려면 `tron_settlement_address`를 대신/함께 보내면 된다.
- 동일한 `external_ref`로 다시 호출해도 안전하다(멱등) — 새로 만들지 않고 기존 자격증명을 그대로 돌려준다.

**응답 (201 Created)**:
```json
{
  "merchant_id": "d903fb38-b5eb-4596-aae6-aaf989e36127",
  "api_key": "sk_test_5c0a6c021fdfb8d344fe00ddfee91edcb3bddcafdeb00736",
  "webhook_secret": "1b8a67a28ced115639cefb2f05441930ed83b10ff3ff9a9ee8a3b067336d791b",
  "terminal_id": "your-service:your-shop-id:default",
  "created": true
}
```

이 응답의 `api_key`와 `terminal_id`, `webhook_secret`을 안전하게 보관하라. `api_key`가 `sk_test_`로 시작하면 샌드박스, `sk_live_`로 시작하면 실서비스다.

---

## 2. 5분 안에 첫 결제 세션 만들기

```bash
curl -X POST https://synapscore.xyz/api/v1/payment-sessions/ \
  -H "X-Api-Key: sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "terminal_id": "your-service:your-shop-id:default",
    "order_number": "ORDER-0001",
    "amount": "1.000000"
  }'
```

**응답 (201 Created)** — `sk_test_` 키로 호출하면 아래처럼 항상 `network`가 테스트넷(`ETH_SEPOLIA`)으로 고정되고 `deposit_targets`도 테스트넷만 담긴다:

```json
{
  "id": "0e7f3dd0-b452-4c31-9e14-0fdbc4a81513",
  "merchant_order_id": "ORDER-0001",
  "amount": "1.000000",
  "currency": "USDT",
  "network": "ETH_SEPOLIA",
  "session_hash": "0x432676fe511f6e2c6f067370038cb03d802cbd01f535f532ee7737aa8f88b9cf",
  "ephemeral_address": "0x13f3e992E2b107eb153cD4b86e60356765C9F46A",
  "status": "PENDING",
  "expires_at": "2026-08-10T08:46:40.456095+09:00",
  "created_at": "2026-08-10T08:16:40.456428+09:00",
  "deposit_targets": [
    {
      "network": "ETH_SEPOLIA",
      "currency": "USDT",
      "strategy": "CREATE2",
      "address": "0x1D18c1d1d14D959765675DB642E4D51D962C18DE",
      "amount_units": "1000000",
      "decimals": 6,
      "gas_token": "ETH",
      "memo": "",
      "is_bound": false
    }
  ],
  "late_deposit_detected": false
}
```

`deposit_targets[].address`가 이 결제를 받을 입금 주소다(체인이 여러 개 활성화돼 있으면 배열에 여러 항목이 온다 — 손님이 그중 하나를 골라 그 주소로 송금). `amount_units`는 최소 단위 정수(예: USDT는 6자리 소수 → `1000000` = 1.0 USDT)다.

### 상태 폴링

```bash
curl https://synapscore.xyz/api/v1/payment-sessions/0e7f3dd0-b452-4c31-9e14-0fdbc4a81513/ \
  -H "X-Api-Key: sk_test_..."
```

`status`가 `PENDING → DETECTED → CONFIRMED → COMPLETED` 순서로 바뀐다. 웹훅(§3)을 받으면 폴링은 선택사항이다.

### 취소

```bash
curl -X POST https://synapscore.xyz/api/v1/payment-sessions/0e7f3dd0-.../cancel/ \
  -H "X-Api-Key: sk_test_..."
```

`PENDING` 상태에서만 취소 가능하다. 입금이 이미 감지됐으면 `409`가 온다(§5 에러 카탈로그).

---

## 3. 웹훅 서명 검증

결제 상태가 바뀌면 등록해둔 `webhook_url`로 Bridge가 POST를 보낸다. 반드시 서명을 검증하라 — 검증 없이 body를 신뢰하면 누구나 가짜 "결제완료" 요청을 보낼 수 있다.

```
POST <your webhook_url>
X-Bridge-Signature: sha256=<hex>
```

서명은 `HMAC-SHA256(webhook_secret, raw_request_body)`다. Python 예제:

```python
import hmac
import hashlib

def verify_bridge_webhook(request_body: bytes, signature_header: str, webhook_secret: str) -> bool:
    if not signature_header.startswith("sha256="):
        return False
    provided = signature_header[len("sha256="):]
    expected = hmac.new(webhook_secret.encode(), request_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, provided)
```

- `request_body`는 **파싱 전 원문 바이트**여야 한다(JSON으로 파싱한 뒤 재직렬화하면 서명이 안 맞는다).
- `webhook_secret`은 §1-1 등록 응답의 그 값이다.
- 검증 실패 시 그냥 무시하라(200이든 4xx든 상관없다 — Bridge는 재전송 정책이 있으므로 계속 재시도된다).

---

## 4. 테스트 모드(샌드박스)

- `mode: "test"`로 등록한 가맹점의 `api_key`는 `sk_test_`로 시작한다.
- 이 키로 만든 모든 결제 세션은 **네트워크가 무엇을 요청하든 자동으로 `ETH_SEPOLIA`(테스트넷)로 강제**된다 — 실수로 메인넷 자금에 영향을 줄 수 없다.
- `deposit_targets`도 테스트넷만 노출된다(메인넷 주소가 섞여 나오지 않는다).
- 테스트용 Sepolia ETH(가스비)·Sepolia USDT는 별도로 확보해야 한다 — 담당자에게 문의하라.
- 실서비스로 전환하려면 `mode: "live"`로 새 가맹점을 등록하거나 담당자에게 전환을 요청하라.

---

## 5. 에러 카탈로그

| HTTP | 의미 | 대응 |
|---|---|---|
| 401 | `X-Api-Key` 없음 또는 잘못됨 | 키 확인. `sk_test_`/`sk_live_` 접두사가 맞는 환경(엔드포인트)인지도 확인 |
| 403 | 프로비저닝 키/토큰이 잘못됨, 만료됨, 회수됨, 또는 소진됨 | 담당자에게 새 토큰 요청 |
| 404 | 존재하지 않는 `terminal_id` 또는 세션 `id` | 요청 값 확인 — `terminal_id`는 가맹점 등록 응답의 값을 그대로 써야 한다 |
| 409 | 이미 입금이 감지된 세션을 취소하려 함 | 취소 대신 상태 폴링으로 전환 대기 |
| 429 | 요청 과다(rate limit) | 잠시 후 재시도. 프로비저닝 엔드포인트는 시간당 한도가 특히 낮다 |

---

## 6. 다음 단계

- 전체 필드/스키마 레퍼런스: `/api/docs/`(Swagger) 또는 `/api/redoc/`
- 멀티체인 입금 타깃(`deposit_targets[]`)의 `strategy`(CREATE2/DIRECT) 차이 등 더 깊은 배경 지식이 필요하면 담당자에게 문의하라.
