Developers
웹훅
웹훅은 주문 상태가 변경되었음을 알려줍니다. 주문 읽기를 대체하지 않습니다: 알림일 뿐이며 진실의 원천이 아닙니다.
게시된 계약, 아직 공개되지 않음
웹훅은 현재 발송되지 않습니다. 모양, 서명 및 재시도 정책은 여기에 확정되어 게시되므로 수신기를 사전에 작성하고 테스트할 수 있습니다.
이벤트
모든 이벤트는 동일한 봉투를 전달합니다: id, type, timestamp 및 data 객체. 처리하는 유형을 구독하고 나머지는 조용히 무시하십시오 — 새 유형이 추가될 것이며, 알 수 없는 유형에서 실패하는 수신기는 스스로를 망가뜨립니다.
| 유형 | 의미 |
|---|---|
order.created | 주문이 생성되고 환율이 고정되었습니다. 입금 주소가 할당되었습니다. |
order.deposit_detected | 수신 거래가 네트워크에서 확인 전에 감지되었습니다. 이 이벤트를 사용하여 사용자를 안심시키고, 어떤 것도 전달하지 마십시오. |
order.confirming | 확인 카운터가 진행 중입니다. 각 블록이 아닌 각 단계에서 발생합니다. |
order.deposit_confirmed | 네트워크에서 요구하는 확인 횟수에 도달했습니다. |
order.underpaid | 수신 금액이 허용 오차를 초과하여 예상 금액보다 적습니다. 주문은 손실되지 않습니다: 추가 입금, 수신 금액으로 계속 진행 또는 환불 중 선택을 기다립니다. |
order.overpaid | 수신 금액이 예상 금액을 초과합니다. 초기 금액은 고정 환율로 유지됩니다. 초과분은 별도로 처리됩니다. |
order.payout_sent | 송금이 결제 수단을 통해 출발했습니다. 주의: 보냄은 수신이 아닙니다 — 남은 지연은 수취인 은행에 따라 다릅니다. |
order.completed | 결제 수단에 의해 결제가 확인되었습니다. 최종 상태입니다. |
order.payout_failed | 결제 수단이 송금을 거부하거나 반환했으며, 그 이유가 있습니다. 은행 반환에 대해 자동 재시도는 이루어지지 않습니다: 원인은 거의 항상 세부 사항에 있습니다. |
order.refunded | 자금이 반환되었으며, 네트워크 수수료를 제외하고 원래 주소로만 반환됩니다. |
페이로드
유형에 관계없이 일정한 봉투입니다.
{
"id": "evt_01J9ZQF3K8N2M4X7",
"type": "order.payout_sent",
"created": 1788356981,
"data": {
"reference": "K7Q4-M2XB",
"state": "PAYOUT_SENT",
"railId": "sepa_instant",
"netMinor": 91228,
"currency": "EUR",
"sentAt": "2026-09-02T12:21:44Z"
}
}서명
모든 전달에는 서명 헤더가 포함됩니다: 타임스탬프와 해당 타임스탬프, 점, 원시 요청 본문에 대해 계산된 HMAC-SHA256.
Fiatside-Signature: t=<timestamp unix>,v1=<hex hmac-sha256>import { createHmac, timingSafeEqual } from 'node:crypto'
// IMPORTANT : le corps doit etre le corps BRUT, avant tout parsing JSON.
// Re-serialiser l'objet change l'ordre des cles et invalide la signature.
export function verify(rawBody: string, header: string, secret: string): boolean {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=') as [string, string]))
const timestamp = Number(parts['t'])
const signature = parts['v1']
if (!timestamp || !signature) return false
// Rejeu : une signature valide capturee hier ne doit pas etre rejouable aujourd'hui.
if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false
const expected = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex')
const a = Buffer.from(expected, 'hex')
const b = Buffer.from(signature, 'hex')
// Comparaison a temps constant : un === laisse fuiter la signature octet par octet.
return a.length === b.length && timingSafeEqual(a, b)
}- 파싱 전에 원시 본문에 서명하십시오. JSON 객체를 다시 직렬화하면 키 순서와 공백이 변경되어 서명이 더 이상 일치하지 않으며 오랫동안 찾게 될 것입니다.
- 일정한 시간에 비교하십시오. === 비교는 첫 번째 다른 바이트에서 중지되므로 공격자가 예상 서명을 바이트 단위로 측정할 수 있습니다.
- 초 허용 오차 창을 초과하면 거부하십시오. 이 검사가 없으면 어제 캡처된 유효한 요청이 오늘도 재생 가능합니다.300-초 허용 오차 창을 초과하면 거부하십시오. 이 검사가 없으면 어제 캡처된 유효한 요청이 오늘도 재생 가능합니다.
- 처리 전에 200으로 응답하십시오. 이벤트를 대기열에 넣고 나중에 처리하십시오: 긴 처리는 시간 초과를 유발하여 이벤트를 수신했더라도 재시도를 초래합니다.
재시도 및 순서
2xx가 아닌 상태 또는 응답 없음은 아래 일정에 따라 재시도를 트리거합니다.
| 시도 | 지연 |
|---|---|
| 1 | 즉시 |
| 2 | 30 s |
| 3 | 2 min |
| 4 | 10 min |
| 5 | 1 h |
| 6 | 6 h |
- 도착 순서는 보장되지 않습니다. 지급 이벤트가 논리적으로 앞서는 확인 이벤트보다 먼저 도착할 수 있습니다: 수신 순서가 아닌 주문 상태를 신뢰하십시오.
- 같은 이벤트가 두 번 도착할 수 있습니다. 이벤트 ID를 저장하고 이미 처리된 ID는 무시하십시오. 수신 측의 멱등성은 귀하의 책임이며, 이를 구현하는 것은 쉽습니다.
- 페이로드의 금액을 신뢰하여 귀하 측에서 어떤 것으로도 적립하지 마십시오. API를 통해 주문을 다시 읽으십시오. 페이로드는 무언가 발생했음을 말하고, API는 정확히 무엇이 발생했는지를 말합니다.
- 마지막 시도 후 이벤트는 삭제되고 이벤트 로그에 계속 표시됩니다. 수동으로 다시 재생할 수 있습니다.