주요 콘텐츠로 이동
Certyneo
개발자 문서

웹후크 실시간 서명 이벤트를 받습니다

Certyneo 대시보드에서 HTTPS URL을 설정하고, HMAC-SHA256로 서명된 POST을 수신합니다. 서면에 어떤 이벤트가 발생하면, 서면, 거부, 종료. 8개의 이벤트가 지원됩니다. 6번 시도에 대해 배수적인 재시험, 8줄의 암호 검증.

< 5s

사건 이후의 평균 배달 기간

5x

실패 시도 (~ 9시간 후까지)

HMAC-SHA256

각 요청에 대한 서명 알고리즘

이벤트 목록

아래 8개의 이벤트는 Certyneo 봉투의 전체 라이프 사이클을 포함합니다. 설정을 통해 관심 있는 이벤트를 활성화하세요 → 웹후크, 다른 이벤트를 무시하세요

사건발발
envelope.createdCRM 측 기록을 생성 시 동기화하는데 유용합니다.
envelope.sent봉투는 서명자에게 보내집니다. 첫 번째 이메일 전송. 활성 서명 순환의 시작을 표시합니다.
envelope.completed모든 서명자들이 서명했습니다. eIDAS 밀폐 PDF와 오디트 트레일은 페이로드에서 `proof_pdf_url`로 사용할 수 있습니다.
envelope.declined서명자가 봉투를 거부했습니다. 거부 이유 (서명자가 제공하면) 는 `data.decline_reason`에 있습니다.
envelope.voided양봉은 발급자가 완전히 서명하기 전에 취소되었습니다.
envelope.expired봉투의 유효기간은 완전히 서명되지 않은 채 지나갔어요. 사라진 서명자의 목록은 `data.missing_signers`에 있습니다.
recipient.signed개별 서명자가 서명했습니다 (하지만 모든 것이 아닙니다). 순차적인 작업 흐름에 유용합니다. 다음 서명자에게 이동을 시작하십시오.
recipient.viewed한 서명자가 서명하지 않고서도 서명 링크를 열었습니다.

페이로드 형식

모든 이벤트들은 같은 상위 레벨 JSON 패턴을 공유합니다: `event`, `envelope_id`, `occurred_at`, `data`. `data`의 내용은 이벤트에 따라 다릅니다.

{
  "event": "envelope.completed",
  "envelope_id": "env_01HG3K8X9Y2N4P5Q6R7S8T9V0W",
  "occurred_at": "2026-05-27T08:42:13.521Z",
  "data": {
    "title": "Contrat de prestation Acme Corp",
    "status": "completed",
    "created_at": "2026-05-24T14:12:00.000Z",
    "completed_at": "2026-05-27T08:42:13.000Z",
    "signers": [
      {
        "email": "client@acme.example",
        "name": "Alex Client",
        "signed_at": "2026-05-27T08:42:13.000Z",
        "method": "eidas_aes",
        "ip": "203.0.113.42"
      }
    ],
    "proof_pdf_url": "https://certyneo.com/api/envelopes/env_01HG.../proof.pdf"
  }
}

페이로드는 BOM 없이 UTF-8로 암호화된다. HMAC 서명은 전송된 바와 같이 의 원형에 계산된다.

HMAC 서명 확인

각 요청은 웹후크 비밀로 서명됩니다. (다시보드에서 생성할 때 한 번만 볼 수 있고, 휴식시엔 암호화됩니다.) 서명은 `Certyneo-Signature` 헤더에서 `t= 형식으로 전송됩니다.<timestamp>v1=<hex_hmac>`. 페이로드 처리 전에 항상 서명 확인하세요. 이 단계 없이는 누구나 이벤트를 만들어서 엔드포인트에 호출할 수 있습니다.

Node.js / TypeScript

import crypto from "node:crypto";

export function verifyCertyneoSignature(
  rawBody: string,
  signatureHeader: string,
  secret: string,
): boolean {
  // Header format: t=<timestamp>,v1=<hex_hmac>
  const parts = Object.fromEntries(
    signatureHeader.split(",").map((p) => p.split("=")),
  );
  const ts = parts.t;
  const sig = parts.v1;
  if (!ts || !sig) return false;

  // Reject events older than 5 minutes (replay protection).
  const age = Math.abs(Date.now() / 1000 - Number(ts));
  if (age > 300) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${ts}.${rawBody}`)
    .digest("hex");

  // timingSafeEqual to mitigate timing attacks.
  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(sig, "hex"),
  );
}

Python

import hashlib
import hmac
import time


def verify_certyneo_signature(
    raw_body: bytes, signature_header: str, secret: str
) -> bool:
    """Verify a Certyneo webhook signature.

    Header format: `t=<timestamp>,v1=<hex_hmac>`
    Rejects events older than 5 minutes (replay protection).
    """
    parts = dict(p.split("=") for p in signature_header.split(","))
    ts = parts.get("t")
    sig = parts.get("v1")
    if not ts or not sig:
        return False

    if abs(time.time() - int(ts)) > 300:
        return False

    expected = hmac.new(
        secret.encode("utf-8"),
        f"{ts}.{raw_body.decode('utf-8')}".encode("utf-8"),
        hashlib.sha256,
    ).hexdigest()

    return hmac.compare_digest(expected, sig)

흔히 발생하는 실수

기대된 서명과 받은 서명을 비교하기 위해 `===` 또는 `==`를 사용하지 마십시오. 시간 안전 함수 (`crypto.timingSafeEqual` in Node, `hmac.compare_digest` in Python) 를 사용하십시오. 그렇지 않으면 두 서명 사이의 비교 시간 간격이 침략자에게 비밀을 점차적으로 공개합니다.

재검토 정책

만약 여러분의 엔드포인트가 HTTP 2xx (시간정지, 5xx, 연결 거부) 를 제외한 다른 응답을 한다면, 우리는 기하급수적인 백업으로 응답합니다. 총 6번 ~9시간 동안 시도합니다. 6번을 마친 후에는 웹 탭에서 이벤트가 `실패`로 표시되고 경고 이메일이 전송됩니다.

시도합쳐진 기간시간이 지났어
#100
#2+ 1 min1 min
#3+ 5 min6 min
#4+ 30 min36 min
#5+ 2 h2h 36
#6+ 6 h8h 36

만약 당신이 실패한 이벤트를 수동으로 전달하고 싶다면, 웹허크 대시보드에는 실패한 모든 배송에 대해 7일 동안 사용할 수 있는 재배송 버튼이 있습니다.

실제 봉투를 보내지 않고 테스트하는 것

엔드포인트 `/v1/webhooks/test`는 URL과 이벤트 타입을 받아, 실제와 정확히 서명된 가상의 페이로드를 POSTe합니다. 지속적인 통합 또는 로컬 개발 (ngrok 또는 동등한) 도중 핸들을 검증하는 데 이상적입니다.

# Trigger a test webhook to your endpoint
curl -X POST https://api.certyneo.com/v1/webhooks/test \
  -H "Authorization: Bearer $CERTYNEO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"event": "envelope.completed", "url": "https://your.app/webhooks/certyneo"}'

6가지 실천방법

  • HMAC 서명을 확인 하려면 BEFORE 모든 본격적인 읽기 시간 안전 비교를 사용 하 여
  • 이미 본 ID를 저장하여 하나의 `envelope_id` × `event`를 처리합니다. (회복은 같은 이벤트를 다시 제공 할 수 있습니다.)
  • HTTP 200에 5초 이내에 응답하고, 그 다음 무동속으로 처리합니다. 그렇지 않으면 타임아웃이 되고 다시 시도로 간주됩니다.
  • 본체 록 + 디버깅에 대한 완전한 서명 HMAC 검사는 종종 BOM 또는 보이지 않는 화면공간에 실패합니다.
  • `실패' 이벤트에 드레터 대기열을 연결해서
  • `t=`와 수신시간 사이 5분 간격을 허용하고, 리플레이를 차단하기 위해 거부합니다.

더 나아가기 위해

시스템 연결 준비 됐어?

2분 안에 무료 계정을 만들 수 있습니다. 웹후크 설정은 스타터 계획에서 사용할 수 있습니다. (무료, 5개 봉투/달).