전자서명 API,
5분 만에 연동
문서 생성 → 서명 링크 발급 → 완료 이벤트 수신.
단 3번의 API 호출로 전자서명 흐름이 완성됩니다.
5분 만에 첫 서명 문서 만들기 (Hosted 플로우)
고객사 백엔드는 API Key로 호스티드 세션만 발급하고, 사용자가 SignPlug의 호스티드 페이지에서 PDF 업로드 · 서명 좌표 지정 · 발송까지 진행합니다. 좌표 계산이나 base64 인코딩이 필요 없습니다.
POST /documents를 참고하세요. Hosted 플로우와 동일하게 모든 환경에서 사용할 수 있습니다.
API Key 발급
이 페이지 상단의 API Key 발급 버튼을 클릭하면 test · live 키 쌍이 즉시 생성됩니다. Moram 계정으로 로그인되어 있어야 합니다. 키는 고객사 백엔드 서버 사이드에서만 사용하고 브라우저에 노출하지 마세요.
호스티드 세션 발급
고객사 백엔드에서 POST /hosted-sessions를 호출해 짧은 수명(기본 1시간) 세션 토큰과 hosted_url을 받습니다.
curl -X POST https://api.moram.net/signplug/v1/hosted-sessions \
-H "Authorization: Bearer sp_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"return_url": "https://your-app.com/contracts/12345/done",
"source_object_key": "pending-sources/018f6f3a-5f7e-7c9a-b111-6b4a7f3c9d22.pdf",
"source_file_type": "pdf",
"metadata": { "order_id": "12345" },
"defaults": {
"title": "공급계약서 2026-04",
"participants": [
{ "name": "홍길동", "phone": "010-1234-5678", "email": "hong@example.com", "role": "signer", "signing_order": 1 }
]
}
}'
응답 예시
{
"session_token": "wsh_Us0GyhIY4IrAXfwlO1uUZhnclHTboxGP",
"hosted_url": "https://signplug.moram.net/start?session_token=wsh_Us0GyhIY4IrAXfwlO1uUZhnclHTboxGP",
"environment": "test",
"expires_at": "2026-05-15T13:00:00Z"
}
사용자를 hosted_url로 리다이렉트
발급받은 hosted_url로 사용자를 보냅니다. 사용자는 SignPlug 호스티드 페이지에서 PDF를 업로드하고 서명 필드를 마우스로 배치한 뒤 발송합니다. 별도 Moram 로그인은 필요 없습니다.
session_token은 URL의 쿼리 파라미터로 전달되므로, 만료 시간이 지나면 자동 무효화됩니다. 동일 토큰으로 여러 문서를 만들 수도 있으니, 사용자별/거래별로 새 세션을 발급하는 것을 권장합니다.
return_url 콜백 처리
문서가 생성되면 SignPlug가 사용자 브라우저를 return_url로 돌려보내며, document_id와 status 쿼리 파라미터를 덧붙입니다.
https://your-app.com/contracts/12345/done
?document_id=a1b2c3d4-e5f6-7890-abcd-ef1234567890
&status=waiting
고객사는 이 콜백에서 document_id를 저장하고, 이후 서명 완료 등은 계정에 등록된 webhook으로 수신합니다. GET /documents/:document_id로 폴링해도 됩니다.
Base URL · 환경 구분
모든 환경이 동일한 Base URL을 사용하고, 환경은 요청 시 사용하는 API Key 프리픽스로만 구분됩니다.
https://api.moram.net/signplug/v1
| 키 프리픽스 | 환경 | 알림톡 OTP | 과금 |
|---|---|---|---|
sp_test_* |
개발 · QA | 실제 발송 | 없음 (테스트 키 OTP 발송량은 합리적인 한도 내에서 무료 제공) |
sp_live_* |
운영 | 실제 발송 | 발생. 활성 결제 수단 등록 필요 |
관련 서비스 URL
| 서비스 | URL | 용도 |
|---|---|---|
| API | https://api.moram.net/signplug/v1 | 모든 REST API 호출 |
| 서명 앱 | https://sign.moram.net/signplug | 참여자가 서명을 진행하는 페이지 (서명 URL의 호스트) |
| 대시보드 | https://signplug.moram.net/dashboard | 키 발급, 결제 수단 등록, 사용량 확인 |
| 랜딩 | https://signplug.moram.net | 제품 소개, 가격, 문의 |
API Key 인증
모든 요청에 Authorization 헤더로 Bearer 토큰을 전달합니다.
# 개발/QA (알림톡 OTP 발송, 과금 없음)
Authorization: Bearer sp_test_xxxxxxxxxxxxxxxxxxxxx
# 운영 (알림톡 OTP 발송, 과금)
Authorization: Bearer sp_live_xxxxxxxxxxxxxxxxxxxxx
호스티드 세션 발급
고객사 백엔드가 API Key로 짧은 수명의 세션 토큰을 발급받고, 사용자를 SignPlug 호스티드 페이지로 리다이렉트합니다. 사용자는 그 페이지에서 PDF 업로드 · 좌표 지정 · 발송을 진행하며, 완료되면 return_url로 돌아옵니다.
/hosted-sessions
Request Body (모두 선택)
| 필드 | 타입 | 설명 |
|---|---|---|
return_url | string | 문서 생성 완료 후 사용자를 돌려보낼 URL. https://만 허용 (개발 시 localhost 예외). 비우면 호스티드 페이지에 머무름 |
metadata | object | 호스티드 세션 식별용 메타데이터. 생성된 문서의 metadata에 병합되어 webhook 등에서 활용 가능 |
source_object_key | string | POST /internal/source-upload로 먼저 업로드한 PDF 키. 전달하면 사용자가 hosted_url에 접속할 때 PDF가 자동 로드됩니다. |
source_file_type | string | 현재 개선형 Hosted 자동 로드는 pdf만 지원합니다. |
expires_in_minutes | integer | 세션 만료 시간(분). 5~240, 기본 60 |
defaults.title | string | 호스티드 페이지의 문서 제목 입력란 기본값 |
defaults.participants | array | 참여자 카드를 미리 채울 값. { name, phone, email, role, signing_order } 형식 |
defaults.options | object | expiration_hours, require_sms_verification, send_notification 등의 기본값 |
Response — 201 Created
{
"session_token": "wsh_Us0GyhIY4IrAXfwlO1uUZhnclHTboxGP",
"hosted_url": "https://signplug.moram.net/start?session_token=wsh_Us0GyhIY4IrAXfwlO1uUZhnclHTboxGP",
"environment": "test",
"expires_at": "2026-05-15T13:00:00.000Z"
}
호스티드 페이지에서의 동작
- 사용자가
hosted_url로 진입하면 자동으로 세션 토큰을 인식해 Moram SSO 없이 접근 가능합니다. defaults에 전달한 값이 문서 제목 · 참여자 카드에 미리 채워집니다.source_object_key가 있으면 PDF가 자동으로 미리보기 화면에 로드되고, 사용자는 업로드 없이 서명 위치만 배치합니다.source_object_key가 없으면 사용자가 PDF 업로드 후 서명 위치를 마우스로 배치하고 "전자서명 시작"을 누르면 문서가 생성됩니다.- 생성 성공 시 SignPlug가 사용자 브라우저를
return_url?document_id=...&status=...로 자동 이동시킵니다. - 이 흐름으로 생성된 문서의 사용량/과금은 세션을 발급한 API Key 환경(test/live)을 따릅니다.
세션 정보 조회 (호스티드 페이지 내부 호출)
/hosted-sessions/current
호스티드 페이지가 세션 토큰을 직접 검증하고 defaults · return_url을 읽어오는 데 사용됩니다. Authorization: Bearer <session_token> 헤더로 호출합니다. 일반적으로 통합 코드에서 직접 호출할 필요는 없습니다.
연결된 PDF 조회 (호스티드 페이지 내부 호출)
/hosted-sessions/current/source.pdf
source_object_key가 연결된 호스티드 세션에서 PDF 미리보기를 렌더링하기 위해 사용합니다. Authorization: Bearer <session_token> 헤더가 필요하며, 연결된 PDF가 없으면 404 source_not_found를 반환합니다.
주요 오류
| HTTP | 코드 | 원인 |
|---|---|---|
400 | invalid_request | return_url이 https가 아니거나 URL 형식 오류 |
400 | unsupported_source_file_type | 호스티드 자동 로드가 지원하지 않는 파일 형식. 현재 pdf만 지원 |
401 | invalid_api_key | 세션 발급은 정식 API Key(sp_test_* 또는 sp_live_*)로만 가능 |
401 | invalid_hosted_session | 호스티드 세션 토큰이 만료/폐기된 경우 (호스티드 페이지 측 오류) |
403 | source_owner_mismatch | source_object_key가 다른 계정 또는 다른 환경에서 생성됨 |
409 | source_already_attached | 이미 다른 호스티드 세션에 연결된 업로드 키 |
409 | source_already_used | 이미 문서 생성에 사용된 업로드 키 |
410 | source_expired | 선업로드된 PDF가 만료됨. 다시 업로드 필요 |
문서 생성 (직접 API 호출)
서버 단일 호출로 PDF · 참여자 · 서명 좌표까지 한 번에 만들고 싶을 때 사용합니다. 좌표 계산을 직접 제어해야 하는 자동화 흐름에 적합합니다. UI 없이 즉시 문서를 만들고 싶지 않다면 위의 호스티드 세션이 더 간단합니다.
/documents
문서 파일, 참여자, 서명 필드를 한 번에 전달해 전자서명 문서를 생성합니다. 성공 시 참여자별 서명 URL을 즉시 반환합니다.
Request Body
| 필드 | 타입 | 설명 | |
|---|---|---|---|
title | string | 필수 | 문서 제목 (예: "공급계약서 2026-04") |
file_base64 | string | 둘 중 하나 | PDF/DOCX/HWPX 파일을 base64 인코딩한 문자열 |
source_object_key | string | 둘 중 하나 | POST /internal/source-upload로 먼저 업로드 후 받은 키 |
file_type | string | 필수 | pdf · docx · hwpx |
participants | array | 필수 | 서명 참여자 목록 (아래 participants 참고). 서명자는 문서당 최대 10명 |
signature_fields | array | 필수 | PDF 내 서명 위치 목록 (아래 signature_fields 참고) |
options | object | 필수 | 만료 시간, 리마인더, OTP 인증 등 옵션. 리마인더가 없으면 [] 전달 |
metadata | object | 필수 | 내부 참조용 메타데이터. 없으면 {} 전달 |
participants[ ] 필드
role이 signer인 추가 서명자는 1명당 150원(VAT 별도)이 추가되며, 한 문서의 서명자는 최대 10명입니다.
| 필드 | 타입 | 설명 | |
|---|---|---|---|
name | string | 필수 | 참여자 표시 이름 |
phone | string|null | 필수 | 휴대폰 번호. 없으면 null 전달 |
email | string|null | 필수 | 이메일 보조 알림에 사용. 없으면 null 전달 |
role | string | 필수 | signer · viewer · approver. signer 역할만 추가 서명자 과금과 최대 10명 제한 대상입니다. |
signing_order | integer | 필수 | 서명 순서 (1부터 시작). 동시 서명은 동일 숫자 지정 |
signature_fields[ ] 필드
| 필드 | 타입 | 설명 | |
|---|---|---|---|
participant_index | integer | 필수 | participants 배열 기준 인덱스 (0부터 시작) |
page | integer | 필수 | 페이지 번호 (1부터 시작) |
x, y | number | 필수 | PDF 기준 좌표 (포인트 단위, 좌상단 원점) |
width, height | number | 필수 | 필드 크기 (포인트 단위) |
type | string | 필수 | signature · name · date |
options 필드
| 필드 | 타입 | 예시값 | 설명 |
|---|---|---|---|
expiration_hours | integer | 72 | 서명 링크 만료 시간 (시간 단위) |
reminder_intervals_hours | array | [] | 리마인더 발송 시점 (시간 단위 배열) |
require_sms_verification | boolean | true | 알림톡 OTP 서명자 인증 필수 여부 |
send_notification | boolean | true | 참여자에게 서명 링크 알림 발송 여부 |
Response — 201 Created
{
"document_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "waiting",
"created_at": "2026-04-23T10:00:00Z",
"expires_at": "2026-04-26T10:00:00Z",
"converted_pdf_url": null,
"signing_urls": [
{
"name": "홍길동",
"participant_index": 0,
"url": "https://sign.moram.net/signplug/s/TOKEN"
}
]
}
파일 업로드 (대용량)
파일이 크거나 base64 변환이 불편할 때는 먼저 파일을 업로드하고 반환된 키를 POST /documents에 사용합니다. multipart/form-data의 file 필드로 최대 50MB까지 업로드할 수 있습니다.
/internal/source-upload
curl -X POST https://api.moram.net/signplug/v1/internal/source-upload \
-H "Authorization: Bearer sp_live_YOUR_KEY" \
-F "file=@contract.pdf" \
-F "file_type=pdf"
Request
| 필드 | 위치 | 설명 | |
|---|---|---|---|
Authorization | header | 필수 | Bearer sp_test_* 또는 Bearer sp_live_* |
file | multipart | 필수 | 업로드할 파일. 최대 50MB |
file_type | multipart | 선택 | pdf · docx · hwpx. 생략하면 파일명 확장자로 자동 추론 |
Response — 201 Created
{
"account_id": "9f2c7c3e-1111-4444-9999-1c4f7a8b9d10",
"source_object_key": "pending-sources/018f6f3a-5f7e-7c9a-b111-6b4a7f3c9d22.pdf",
"bucket_name": "signplug-documents",
"file_type": "pdf",
"original_filename": "contract.pdf",
"storage_provider": "s3",
"expires_at": "2026-05-20T10:00:00.000Z",
"size": 123456,
"sha256": "e3b0c44298fc1c149afb..."
}
응답 필드
| 필드 | 설명 |
|---|---|
source_object_key | POST /hosted-sessions 또는 POST /documents의 source_object_key 필드에 그대로 전달 |
expires_at | 선업로드 키 만료 시각. 만료 후에는 다시 업로드해야 합니다. |
sha256 | 업로드된 파일 원본의 SHA-256 해시. 클라이언트에서 무결성을 검증할 때 사용 |
size | 바이트 단위 파일 크기 |
storage_provider | s3 또는 minio. 디버깅용 정보 |
주요 오류
| HTTP | 코드 | 원인 |
|---|---|---|
400 | invalid_request | file 필드 없음 · 빈 파일 · file_type이 pdf/docx/hwpx가 아님 |
413 | FST_REQ_FILE_TOO_LARGE | 업로드 파일이 50MB(52,428,800 bytes)를 초과 |
401 | invalid_api_key | API Key 누락 또는 잘못된 키 |
source_object_key는 동일 계정의 POST /hosted-sessions에 연결하거나 POST /documents 호출에서 사용할 수 있습니다. 한 번 문서 생성에 사용되면 정식 문서 스토리지로 이동합니다. 사용되지 않은 키는 기본 24시간 후 만료되므로 발급 후 가급적 빠르게 사용하세요.
문서 목록 조회
계정의 문서를 최신 생성순으로 조회합니다. 대시보드와 운영 모니터링에서 사용하는 API입니다.
/documents?page=1&limit=20&status=completed
쿼리 파라미터
| 쿼리 | 타입 | 기본값 | 설명 |
|---|---|---|---|
page | integer | 1 | 1부터 시작하는 페이지 번호 |
limit | integer | 20 | 페이지당 항목 수. 1~100 사이 |
status | string | - | 특정 상태만 필터. 가능한 값: draft, waiting, in_progress, completed, rejected, expired, cancelled |
curl "https://api.moram.net/signplug/v1/documents?page=1&limit=20&status=completed" \
-H "Authorization: Bearer sp_live_YOUR_KEY"
Response — 200 OK
{
"documents": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"title": "공급계약서 2026-04",
"status": "completed",
"environment": "live",
"source_file_type": "pdf",
"created_at": "2026-04-23T10:00:00.000Z",
"expires_at": "2026-04-26T10:00:00.000Z",
"completed_at": "2026-04-23T11:42:10.000Z"
},
{
"id": "0b1c2d3e-4f56-7890-abcd-ef1234567891",
"title": "비밀유지서약서",
"status": "waiting",
"environment": "live",
"source_file_type": "docx",
"created_at": "2026-04-22T09:00:00.000Z",
"expires_at": "2026-04-25T09:00:00.000Z",
"completed_at": null
}
],
"total": 47,
"page": 1,
"limit": 20
}
(page * limit) < total로 판단합니다. 대시보드용 무한 스크롤은 page를 증가시키며 호출하세요. environment 필드로 test/live 문서를 구분할 수 있습니다.
문서 상태 조회
/documents/:document_id
curl https://api.moram.net/signplug/v1/documents/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "Authorization: Bearer sp_live_YOUR_KEY"
Response — 200 OK
{
"document_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"title": "공급계약서 2026-04",
"status": "in_progress",
"created_at": "2026-04-23T10:00:00.000Z",
"completed_at": null,
"metadata": { "order_id": "12345" },
"participants": [
{ "name": "홍길동", "status": "signed", "signed_at": "2026-04-23T10:14:32.000Z" },
{ "name": "김영희", "status": "verified", "signed_at": null }
]
}
참여자별 status는 pending · viewed · verified · signed · rejected · expired 중 하나이며, 문서의 status와는 별도 흐름입니다.
/documents/:document_id/pdf
완료된 문서의 최종 PDF를 application/pdf로 내려받습니다. 브라우저 표시용 Content-Disposition: inline 헤더가 포함되어 있어, 그대로 <iframe>이나 새 탭에 표시할 수 있습니다. 파일을 강제로 저장하게 하려면 응답을 그대로 전달하지 말고 자체 프록시에서 attachment로 다시 내려주세요.
변환·서명 완료 전에 호출하면 409 document_pdf_not_ready가 반환됩니다.
/documents/:document_id/audit-log
문서 해시와 열람·OTP 인증·서명·거절·취소 이벤트를 시간순으로 반환합니다.
Response — 200 OK
{
"document_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"document_hash": "8b1a9953c4611296a827abf8c47804d7...",
"events": [
{
"event_type": "document.viewed",
"timestamp": "2026-04-23T10:08:15.000Z",
"participant_name": "홍길동",
"ip_address": "203.0.113.42",
"user_agent": "Mozilla/5.0 ...",
"verification_method": null
},
{
"event_type": "document.verified",
"timestamp": "2026-04-23T10:09:01.000Z",
"participant_name": "홍길동",
"ip_address": "203.0.113.42",
"user_agent": "Mozilla/5.0 ...",
"verification_method": "alimtalk_otp"
},
{
"event_type": "document.signed",
"timestamp": "2026-04-23T10:14:32.000Z",
"participant_name": "홍길동",
"ip_address": "203.0.113.42",
"user_agent": "Mozilla/5.0 ...",
"verification_method": "canvas"
}
]
}
/documents/:document_id/audit-certificate.pdf
완료된 문서의 감사추적 인증서를 PDF로 내려받습니다. 인증서에는 문서 해시(원본·변환·최종), 참여자별 인증/서명 일시, IP, OTP 검증 방법이 포함되어 분쟁 대응 자료로 활용할 수 있습니다. 문서가 completed 상태가 아니면 인증서가 생성되지 않습니다.
/documents/:document_id
아직 완료되지 않은 문서를 취소합니다. 진행 중인 서명 링크는 즉시 무효화되며, 등록된 webhook에 document.cancelled 이벤트가 발송됩니다. completed · expired · rejected · 이미 cancelled 상태인 문서에 호출하면 409 invalid_state가 반환됩니다.
Response — 200 OK
{
"document_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "cancelled",
"cancelled_at": "2026-04-23T15:42:10.000Z"
}
status 값
| 값 | 설명 |
|---|---|
draft | DOCX/HWPX 변환 등 준비 중. 이 상태에서는 /pdf 호출이 409 |
waiting | 모든 참여자가 아직 서명하지 않음. 서명 링크 유효 |
in_progress | 일부 참여자가 서명 완료. 순차 서명일 경우 다음 차례 참여자에게 알림 발송 |
completed | 모든 참여자 서명 완료. /pdf, /audit-certificate.pdf 모두 다운로드 가능 |
rejected | 참여자 중 한 명이 서명을 거절. 후속 참여자 알림은 발송하지 않음 |
expired | 만료 기간 초과. 서명 링크 사용 불가 |
cancelled | DELETE 호출로 취소됨 |
서명 링크 · 참여자 경험
문서 생성 응답의 signing_urls[].url을 참여자에게 그대로 전달하면 SignPlug 서명 앱이 알림톡 OTP 인증부터 서명 제출, 완료 안내까지 전 과정을 처리합니다. 아래 /public/signing-links/* 엔드포인트는 서명 앱이 내부적으로 사용하므로 일반 통합에서는 직접 호출할 필요가 없습니다.
서명 URL 형식
https://sign.moram.net/signplug/s/<TOKEN>
토큰은 문서 생성 시 참여자별로 발급되며, 만료 시간이 지나면 자동 무효화됩니다. SignPlug 서명 앱은 모바일·태블릿·데스크탑 환경을 자동 감지해 화면을 최적화하고, 알림톡(delivery_status: alimtalk)이 실패한 참여자에게는 이메일(email) OTP로 폴백합니다.
참여자에게 안내 메시지를 직접 보낼 때 예시
안녕하세요, [회사명]입니다.
"[문서 제목]" 전자서명 요청을 보내드립니다.
▶ 서명하러 가기: https://sign.moram.net/signplug/s/abcd1234...
· 본인 휴대폰으로 발송되는 알림톡 OTP 인증 후 서명할 수 있습니다.
· 링크는 발송일로부터 72시간 동안 유효합니다.
서명 앱 내부 엔드포인트 (참고용)
자체 서명 UI를 구축하는 특수한 케이스가 아니라면 호출할 필요가 없습니다. 인증은 token 기반이며 별도의 API Key를 요구하지 않습니다.
| 엔드포인트 | 용도 |
|---|---|
GET /public/signing-links/:token | 문서 정보, 참여자 상태, 서명 필드, 워크플로 상태(is_current_turn 등) 조회 |
GET /public/signing-links/:token/pdf | 서명 대상 PDF 다운로드 (inline) |
GET /public/signing-links/:token/preview-pages | 모바일 미리보기용 이미지 페이지 생성 |
POST /public/signing-links/:token/otp/request | 알림톡 OTP 발송. 실패 시 이메일로 폴백 |
POST /public/signing-links/:token/otp/verify | OTP 검증. 검증 성공 시 participant.status = verified |
POST /public/signing-links/:token/signature | 서명 이미지 제출 및 문서 완료. body: { agree_terms, method, signature_base64 } |
POST /public/signing-links/:token/reject | 서명 거절. body: { reason } |
window.open(url, '_blank')으로 새 창에 띄우거나 사용자를 해당 URL로 리다이렉트하는 패턴을 권장합니다.
서명 진행 이벤트 수신
문서 생성, 열람, OTP 인증, 서명, 완료, 거절, 만료, 취소 이벤트를 등록한 URL로 수신합니다.
/webhooks
Webhook 등록
curl -X POST https://api.moram.net/signplug/v1/webhooks \
-H "Authorization: Bearer sp_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/signplug",
"events": ["document.created", "document.signed", "document.completed"],
"timeout_seconds": 10
}'
운영 환경에서는 https:// URL만 등록할 수 있습니다. secret을 생략하면 whsec_* 값이 자동 발급되어 응답에 한 번 표시됩니다.
수신 Payload 예시
POST https://yourapp.com/webhooks/signplug
Content-Type: application/json
X-SignPlug-Delivery-Id: 018f6f3a-5f7e-7c9a-b111-6b4a7f3c9d22
X-SignPlug-Event: document.completed
X-SignPlug-Signature: t=2026-04-23T15:42:10.000Z,v1=...
X-SignPlug-Timestamp: 2026-04-23T15:42:10.000Z
{
"event_type": "document.completed",
"event_key": "document.completed:a1b2c3d4-e5f6-7890-abcd-ef1234567890:018f6f3a",
"occurred_at": "2026-04-23T15:42:10.000Z",
"data": {
"document": {
"document_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"environment": "live",
"metadata": { "source": "portal" },
"status": "completed",
"title": "테스트 계약서"
},
"participant": null,
"event_data": {
"signed_participants": 2
}
}
}
이벤트 종류
| 이벤트 | 발생 시점 |
|---|---|
document.created | 문서 생성 완료 |
document.viewed | 참여자가 서명 링크 열람 |
document.verified | 참여자가 OTP 인증 완료 |
document.signed | 참여자 한 명이 서명 완료 |
document.completed | 모든 참여자 서명 완료 |
document.rejected | 참여자가 서명 거절 |
document.expired | 서명 링크 만료 |
document.reminded | 리마인더 발송 |
document.cancelled | 문서 취소 |
HMAC 서명 검증 (Python)
import hashlib, hmac
def verify_webhook(payload_bytes: bytes, signature_header: str, secret: str) -> bool:
parts = dict(item.split("=", 1) for item in signature_header.split(",") if "=" in item)
timestamp = parts.get("t")
received = parts.get("v1")
if not timestamp or not received:
return False
signed_payload = timestamp.encode() + b"." + payload_bytes
expected = hmac.new(secret.encode(), signed_payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, received)
# Flask 예시
@app.route("/webhooks/signplug", methods=["POST"])
def webhook():
sig = request.headers.get("X-SignPlug-Signature", "")
if not verify_webhook(request.get_data(), sig, WEBHOOK_SECRET):
return "Unauthorized", 401
event = request.json
if event["event_type"] == "document.completed":
# 완료 처리 로직
pass
return "OK", 200
운영 · 과금 · 키 관리
라이브 키로 문서를 생성하려면 계정에 활성 결제 수단이 있어야 합니다. 미등록 상태에서는 402 payment_method_required가 반환됩니다.
API Key 관리
| 엔드포인트 | 용도 |
|---|---|
GET /api-keys | 계정의 test/live API Key 목록 조회. 키 원문은 재표시되지 않으며 key_prefix(앞 7~12자)로 구분합니다. |
POST /api-keys/:api_key_id/rotate | 기존 키를 비활성화하고 같은 환경의 새 키를 발급. 새 키 원문은 응답에 1회 표시 |
POST /api-keys/:api_key_id/disable | 키 비활성화. 환경별 마지막 활성 키는 먼저 rotate 해야 함 |
GET /api-keys 응답 예시
{
"api_keys": [
{
"id": "5f7a1c2b-1111-4444-9999-1c4f7a8b9d10",
"name": "Default Test Key",
"environment": "test",
"key_prefix": "sp_test_",
"is_active": true,
"is_current": true,
"created_at": "2026-04-23T10:00:00.000Z",
"last_used_at": "2026-04-23T15:42:10.000Z",
"expires_at": null
},
{
"id": "6a8b2d3c-2222-5555-aaaa-2d5f8b9caef1",
"name": "Default Live Key",
"environment": "live",
"key_prefix": "sp_live_",
"is_active": true,
"is_current": true,
"created_at": "2026-04-23T10:00:00.000Z",
"last_used_at": null,
"expires_at": null
}
]
}
POST /api-keys/:id/rotate 응답 예시
{
"api_key_id": "8c9d4e5f-3333-6666-bbbb-3e6f9cadef02",
"api_key": "sp_live_AbCdEf...전체키...XyZ",
"name": "Default Live Key",
"environment": "live",
"created_at": "2026-05-15T10:00:00.000Z"
}
api_key 원문은 이 응답에서만 확인할 수 있으므로 즉시 안전한 저장소(서버 시크릿 매니저 등)에 보관하세요. 회전 후 기존 키는 자동으로 비활성화됩니다.
결제 · 사용량
| 엔드포인트 | 용도 |
|---|---|
GET /billing/method | 현재 활성 결제 수단 조회 (없으면 null) |
GET /billing/usage?year=YYYY&month=M | 해당 월의 문서 수, API/웹 분리 카운트, 청구 금액, 청구 상태 조회 |
GET /billing/outstanding | 아직 청구되지 않은 누적 사용량과 금액 |
GET /billing/web-entitlement | 웹 결제 무료 문서 잔여 수, 결제 수단 등록 필요 여부 |
GET /billing/config | Toss/Paddle 결제 설정 가능 여부 조회 (인증 불필요) |
GET /billing/usage 응답 예시
{
"doc_count": 23,
"api_doc_count": 18,
"web_doc_count": 5,
"free_doc_count": 5,
"amount_krw": 8400,
"billing_status": "pending",
"billed_at": null
}
billing_status는 pending · billed · failed 중 하나입니다. free_doc_count는 무료 집계분이므로 amount_krw 산정에서 제외됩니다.
GET /billing/method 응답 예시
{
"provider": "toss",
"card_company": "신한",
"card_number_masked": "************1234",
"customer_key": "cust_018f6f3a...",
"subscription_id": null,
"is_active": true
}
오류 코드
모든 오류는 동일한 형식으로 반환됩니다.
// 오류 응답 형식
{
"error": "invalid_request",
"message": "file_type must be one of: pdf, docx, hwpx.",
"details": null
}
details는 필드 단위 검증 오류를 포함할 수 있는 객체 또는 null입니다. error 코드(기계 판독용)로 분기하고, message는 로그/디버깅 용도로 사용하세요.
| HTTP | 코드 | 원인 |
|---|---|---|
400 | invalid_request | 필수 필드 누락, 잘못된 파일 형식, 서명자 10명 초과, 잘못된 Webhook URL 등 |
400 | agreement_required | 서명 제출 시 약관 동의 누락 |
401 | missing_authorization | Authorization 헤더 없음 |
401 | invalid_authorization | Bearer 형식이 아님 |
401 | invalid_api_key | API Key가 잘못되었거나 비활성화됨 |
402 | payment_method_required | live 문서 생성에 필요한 결제 수단 없음. 대시보드에서 카드 등록 후 재시도 |
404 | document_not_found | 문서 ID가 없거나, 다른 계정의 문서라서 권한이 없음 |
404 | signing_link_not_found | 서명 토큰을 찾을 수 없음. 잘못된 토큰이거나 이미 폐기됨 |
409 | document_pdf_not_ready | 변환 또는 최종 PDF 생성이 아직 완료되지 않음. 잠시 후 재시도 |
409 | participant_not_verified | OTP 인증 전 서명 제출. 먼저 /otp/verify 호출 필요 |
409 | invalid_state | 완료/만료/취소된 문서에 DELETE를 호출하는 등 상태가 맞지 않음 |
410 | document_expired | 서명 가능 기간 만료 |
413 | FST_REQ_FILE_TOO_LARGE | 업로드 파일이 50MB 한도를 초과. multipart 응답 헤더로 반환 |
429 | test_quota_exhausted | 테스트 키의 알림톡 OTP 누적 발송 한도(기본 50건) 초과. 한도 증가는 contact@moram.net으로 문의. details.sent·details.limit으로 현재 사용량 확인 가능 |
500 | internal_server_error | 서버 내부 오류. 동일 요청을 즉시 재시도하지 말고 contact@moram.net으로 시간/요청 ID 함께 문의 |
409 document_pdf_not_ready처럼 일시적인 상태 오류는 지수 백오프(예: 1s → 2s → 4s)로 재시도하면 됩니다. 4xx 오류 중 invalid_request, invalid_api_key, document_not_found 등은 클라이언트 측 입력 문제이므로 재시도하지 마세요.
자주 묻는 질문 · 통합 팁
PDF 좌표를 어떻게 알 수 있나요?
웹 서명 도구에서 마우스로 필드를 배치하면 정확한 x, y, width, height 값을 확인할 수 있습니다. 이 값을 그대로 API에 넘기세요.
순차 서명 vs 동시 서명
signing_order를 서로 다르게 지정하면 순차 서명, 동일하게 지정하면 동시 서명입니다. 예: [1, 1]이면 두 사람 모두 즉시 링크 수신.
DOCX · HWPX 지원
DOCX, HWPX 파일은 서버에서 자동으로 PDF 변환 후 처리됩니다. 변환 중에는 문서 상태가 draft일 수 있으므로 완료 PDF 조회 전에 상태를 확인하세요.
파일이 크거나 base64가 불편할 때
POST /internal/source-upload로 먼저 파일을 업로드하고, 받은 source_object_key를 POST /hosted-sessions 또는 POST /documents에 전달하면 base64 인코딩 없이 대용량 파일을 처리할 수 있습니다.
Test 환경에서 OTP 수신 확인
Test 키로 발급된 문서도 알림톡 OTP가 실제로 발송됩니다. 참여자의 phone 필드에 정확한 휴대폰 번호를 넣어야 OTP를 받을 수 있습니다.
Webhook을 받지 못할 때
서버가 설정된 제한 시간 안에 2xx를 응답하지 않으면 재시도됩니다. 운영 환경에서는 https:// URL만 등록할 수 있으므로 로컬 개발 시에는 ngrok이나 유사 터널링 도구로 임시 URL을 생성하세요.