Developer Docs

전자서명 API,
기존 시스템에 연동

문서 생성 → 서명 링크 발급 → 완료 이벤트 수신.
단 3번의 API 호출로 전자서명 흐름이 완성됩니다.

빠른 시작 →
1
문서 생성 POST /documents
→
2
링크 전달 signing_urls[] 반환
→
3
완료 수신 Webhook 이벤트
Quick Start

5분 만에 첫 서명 문서 만들기 (Hosted 플로우)

고객사 백엔드는 API Key로 호스티드 세션만 발급하고, 사용자가 SignPlug의 호스티드 페이지에서 PDF 업로드 · 서명 좌표 지정 · 발송까지 진행합니다. 좌표 계산이나 base64 인코딩이 필요 없습니다.

좌표·base64를 직접 다루며 서버에서 단일 호출로 문서를 만들고 싶다면 아래 문서 생성 섹션의 POST /documents를 참고하세요. Hosted 플로우와 동일하게 모든 환경에서 사용할 수 있습니다.
API 고객사는 서명자 개인정보의 적법한 수집 주체이며, SignPlug는 전자서명 요청, 알림 발송, 문서 보관, 로그 생성을 위해 위탁받은 목적 범위 안에서만 처리합니다. 자세한 내용은 SignPlug 개인정보 처리방침와 서비스 약관을 확인하세요.
현행 연동 기준: 모든 문서 생성 요청은 recipient_consent_confirmed: true와 send_notification: true가 필요합니다. email 또는 phone 중 하나 이상 필수입니다. 휴대폰만 등록한 열람자·검토자는 자동 알림이 없으므로 응답의 참여자 링크를 직접 전달해야 합니다. 참여자별 require_identity_verification으로 본인인증 여부를 지정합니다. 인증을 사용하는 서명자에게만 휴대폰 번호가 필요합니다. 이름 도장 생성은 수신자가 SignPlug 서명 앱에서 선택하는 기능이므로 문서 생성 API 요청 형식은 변경되지 않습니다.
개발/운영 환경: sp_test_* 테스트 키는 KG이니시스 개발 본인확인 채널을 사용하며, 전자서명 생성은 무제한입니다. sp_live_* 라이브 키와 SignPlug 웹은 KG이니시스 운영 본인확인 채널을 사용합니다.
1

API Key 발급

이 페이지 상단의 API Key 발급 버튼을 클릭하면 누락된 키가 생성됩니다. SignPlug 로그인 후 테스트 키를 발급할 수 있으며, 라이브 키는 활성 결제수단 등록 후 발급됩니다. 키는 고객사 백엔드 서버 사이드에서만 사용하고 브라우저에 노출하지 마세요.

2

호스티드 세션 발급

고객사 백엔드에서 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 }
      ],
      "options": {
        "require_sms_verification": true,
        "recipient_consent_confirmed": true,
        "recipient_consent_note": "회원가입 약관 동의",
        "send_notification": true
      }
    }
  }'

응답 예시

{
  "session_token": "wsh_Us0GyhIY4IrAXfwlO1uUZhnclHTboxGP",
  "hosted_url": "https://signplug.moram.net/start?session_token=wsh_Us0GyhIY4IrAXfwlO1uUZhnclHTboxGP",
  "environment": "test",
  "expires_at": "2026-05-15T13:00:00Z"
}
3

사용자를 hosted_url로 리다이렉트

발급받은 hosted_url로 사용자를 보냅니다. 사용자는 SignPlug 호스티드 페이지에서 PDF를 업로드하고 서명 필드를 마우스로 배치한 뒤 발송합니다. 별도 Moram 로그인은 필요 없습니다.

session_token은 URL의 쿼리 파라미터로 전달되므로, 만료 시간이 지나면 자동 무효화됩니다. 동일 토큰으로 여러 문서를 만들 수도 있으니, 사용자별/거래별로 새 세션을 발급하는 것을 권장합니다.
4

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 · 환경 구분

모든 환경이 동일한 Base URL을 사용하고, 환경은 요청 시 사용하는 API Key 프리픽스로만 구분됩니다.

https://api.moram.net/signplug/v1
키 프리픽스환경본인확인과금
sp_test_* 개발 · QA 실제 발송 없음 (테스트 환경 본인확인 시도는 내부 정책 한도 내에서 제공)
sp_live_* 운영 실제 발송 발생. 활성 결제수단 등록 후 발급 및 사용 가능
테스트 키 본인확인: 테스트 키에서도 KG이니시스 본인확인 창이 호출될 수 있습니다. 대량 테스트나 한도 상향이 필요하면 contact@moram.net으로 문의하세요.

관련 서비스 URL

서비스URL용도
APIhttps://api.moram.net/signplug/v1모든 REST API 호출
서명 앱https://sign.moram.net/signplug참여자가 서명을 진행하는 페이지 (서명 URL의 호스트)
대시보드https://signplug.moram.net/dashboard키 발급, 결제 수단 등록, 사용량 확인
랜딩https://signplug.moram.net제품 소개, 가격, 문의
Authentication

API Key 인증

모든 요청에 Authorization 헤더로 Bearer 토큰을 전달합니다.

# 개발/QA (KG이니시스 본인확인 테스트, 과금 없음)
Authorization: Bearer sp_test_xxxxxxxxxxxxxxxxxxxxx

# 운영 (KG이니시스 본인확인, 과금)
Authorization: Bearer sp_live_xxxxxxxxxxxxxxxxxxxxx
보안 주의: API Key를 프론트엔드(브라우저) 코드에 직접 포함하지 마세요. 서버 사이드에서만 사용하거나, 내부 프록시 엔드포인트로 감싸세요.
Hosted Sessions

호스티드 세션 발급

고객사 백엔드가 API Key로 짧은 수명의 세션 토큰을 발급받고, 사용자를 SignPlug 호스티드 페이지로 리다이렉트합니다. 사용자는 그 페이지에서 PDF 업로드 · 좌표 지정 · 발송을 진행하며, 완료되면 return_url로 돌아옵니다.

POST /hosted-sessions

Request Body (모두 선택)

필드타입설명
return_urlstring 문서 생성 완료 후 사용자를 돌려보낼 URL. https://만 허용 (개발 시 localhost 예외). 비우면 호스티드 페이지에 머무름
metadataobject 호스티드 세션 식별용 메타데이터. 생성된 문서의 metadata에 병합되어 webhook 등에서 활용 가능
source_object_keystring POST /internal/source-upload로 먼저 업로드한 PDF 키. 전달하면 사용자가 hosted_url에 접속할 때 PDF가 자동 로드됩니다.
source_file_typestring 현재 개선형 Hosted 자동 로드는 pdf만 지원합니다.
expires_in_minutesinteger 세션 만료 시간(분). 5~240, 기본 60
defaults.titlestring 호스티드 페이지의 문서 제목 입력란 기본값
defaults.participantsarray 참여자 카드를 미리 채울 값. { name, phone, email, role, signing_order } 형식
defaults.optionsobject expiration_hours, require_sms_verification, recipient_consent_confirmed, send_notification 등의 기본값. require_sms_verification은 호환용 필드명이며 참여자의 require_identity_verification 생략 시 적용되는 기본 인증 정책입니다.

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)을 따릅니다.

세션 정보 조회 (호스티드 페이지 내부 호출)

GET /hosted-sessions/current

호스티드 페이지가 세션 토큰을 직접 검증하고 defaults · return_url을 읽어오는 데 사용됩니다. Authorization: Bearer <session_token> 헤더로 호출합니다. 일반적으로 통합 코드에서 직접 호출할 필요는 없습니다.

연결된 PDF 조회 (호스티드 페이지 내부 호출)

GET /hosted-sessions/current/source.pdf

source_object_key가 연결된 호스티드 세션에서 PDF 미리보기를 렌더링하기 위해 사용합니다. Authorization: Bearer <session_token> 헤더가 필요하며, 연결된 PDF가 없으면 404 source_not_found를 반환합니다.

주요 오류

HTTP코드원인
400invalid_requestreturn_url이 https가 아니거나 URL 형식 오류
400unsupported_source_file_type호스티드 자동 로드가 지원하지 않는 파일 형식. 현재 pdf만 지원
401invalid_api_key세션 발급은 정식 API Key(sp_test_* 또는 sp_live_*)로만 가능
401invalid_hosted_session호스티드 세션 토큰이 만료/폐기된 경우 (호스티드 페이지 측 오류)
403source_owner_mismatchsource_object_key가 다른 계정 또는 다른 환경에서 생성됨
409source_already_attached이미 다른 호스티드 세션에 연결된 업로드 키
409source_already_used이미 문서 생성에 사용된 업로드 키
410source_expired선업로드된 PDF가 만료됨. 다시 업로드 필요
Create Document

문서 생성 (직접 API 호출)

서버 단일 호출로 PDF · 참여자 · 서명 좌표까지 한 번에 만들고 싶을 때 사용합니다. 좌표 계산을 직접 제어해야 하는 자동화 흐름에 적합합니다. UI 없이 즉시 문서를 만들고 싶지 않다면 위의 호스티드 세션이 더 간단합니다.

POST /documents

문서 파일, 참여자, 서명 필드를 한 번에 전달해 전자서명 문서를 생성합니다. 성공 시 참여자별 서명 URL을 즉시 반환합니다.

Request Body

필드타입설명
titlestring 필수 문서 제목 (예: "공급계약서 2026-04")
file_base64string 둘 중 하나 PDF 파일을 base64 인코딩한 문자열 (PDF 파일만 지원)
source_object_keystring 둘 중 하나 POST /internal/source-upload로 먼저 업로드 후 받은 키
file_typestring 필수 pdf 고정. 다른 값은 400 unsupported_file_type
participantsarray 필수 서명 참여자 목록 (아래 participants 참고). 서명자는 문서당 최대 10명
signature_fieldsarray 필수 PDF 내 서명 위치 목록 (아래 signature_fields 참고)
coordinate_originstring 선택 좌표 원점. 기본값은 top_left이며 기존 좌하단 좌표 연동은 bottom_left 지정.
optionsobject 필수 만료 시간, 리마인더, 본인확인, 수신자 사전 동의 확인 등 옵션. recipient_consent_confirmed는 true가 필요합니다.
metadataobject 선택 내부 참조용 메타데이터. 생략하면 {}로 처리

participants[ ] 필드

문서 1건에는 서명자 2명 · 알림톡·이메일 안내 · 본인인증이 기본 포함됩니다. role이 signer인 3명째 서명자부터 1명당 100원(VAT 포함)이 추가되며, 한 문서의 서명자는 최대 10명입니다.
필드타입설명
namestring필수참여자 표시 이름
require_identity_verificationboolean선택서명자별 본인인증. false이면 이메일 링크로 본인인증 없이 서명합니다. 생략 시 options.require_sms_verification(기본 true)을 상속합니다. 요청자가 발송 전에 정하며 수신자가 변경할 수 없습니다. 인증 미실시는 감사기록과 인증서에 구분됩니다.
phonestring|null조건부 필수휴대폰 번호. 이메일이 없거나 본인인증을 사용하는 서명자는 필수입니다. 국내 휴대폰 번호를 입력하세요. 번호가 없으면 알림톡 작업을 생성하지 않습니다.
emailstring|null조건부 필수휴대폰과 둘 중 하나 이상 필수. 이메일을 등록하면 요청·완료 안내와 완료 문서·감사추적 인증서를 이메일로 받습니다.
rolestring필수signer · viewer · approver. signer 역할만 추가 서명자 과금과 최대 10명 제한 대상입니다.
signing_orderinteger필수서명 순서 (1부터 시작). 동시 서명은 동일 숫자 지정

signature_fields[ ] 필드

필드타입설명
participant_indexinteger필수participants 배열 기준 인덱스 (0부터 시작)
pageinteger필수페이지 번호 (1부터 시작)
x, ynumber필수PDF 기준 좌표 (포인트 단위, 기본 좌상단 원점). 변환 문서는 변환된 PDF 페이지 기준
width, heightnumber필수필드 크기 (포인트 단위)
typestring필수signature · name · date

options 필드

필드타입예시값설명
expiration_hoursinteger72서명 링크 만료 시간 (시간 단위)
reminder_intervals_hoursarray[]리마인더 발송 시점 (시간 단위 배열). 리마인더가 없으면 생략하거나 [] 전달
require_sms_verificationbooleantrue문서의 기본 본인인증 정책. 기본값 true이며 false도 허용합니다. 참여자의 require_identity_verification 값이 우선합니다. 기존 문서에는 기존 정책이 유지됩니다.
recipient_consent_confirmedbooleantrue서명 요청 수신자가 전자문서 수신 및 전자서명 진행에 사전 동의했음을 발송자가 확인했는지 여부. 문서 생성 시 true가 필요합니다.
recipient_consent_notestring|nullnull사전 동의 확인 경로나 근거 메모. 예: "회원가입 약관 동의", "계약 담당자 이메일 회신".
send_notificationbooleantrue이메일 안내 발송 여부. 이메일이 있으면 이메일을, 휴대폰을 입력한 서명자에게는 알림톡을 발송합니다. 열람자·검토자는 이메일이 없으면 자동 알림이 없습니다. false는 허용되지 않습니다.

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"
    }
  ]
}
좌표 팁: 서명 필드 위치(x, y)는 PDF 포인트 기준입니다. A4 용지는 가로 595pt × 세로 842pt입니다. 웹 서명 도구를 열면 마우스로 직접 배치하고 좌표를 확인할 수 있습니다.
File Upload

파일 업로드 (대용량)

파일이 크거나 base64 변환이 불편할 때는 먼저 파일을 업로드하고 반환된 키를 POST /documents에 사용합니다. multipart/form-data의 file 필드로 최대 50MB까지 업로드할 수 있습니다.

POST /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

필드위치설명
Authorizationheader 필수 Bearer sp_test_* 또는 Bearer sp_live_*
filemultipart 필수 업로드할 파일. 최대 50MB
file_typemultipart 선택 pdf 고정. 생략하면 파일명 확장자(.pdf)로 자동 추론. 다른 형식은 400 unsupported_file_type

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_keyPOST /hosted-sessions 또는 POST /documents의 source_object_key 필드에 그대로 전달
expires_at선업로드 키 만료 시각. 만료 후에는 다시 업로드해야 합니다.
sha256업로드된 파일 원본의 SHA-256 해시. 클라이언트에서 무결성을 검증할 때 사용
size바이트 단위 파일 크기
storage_providers3 또는 minio. 디버깅용 정보

주요 오류

HTTP코드원인
400invalid_requestfile 필드 없음 · 빈 파일 · file_type이 pdf가 아님(비-PDF 파일은 unsupported_file_type)
413FST_REQ_FILE_TOO_LARGE업로드 파일이 50MB(52,428,800 bytes)를 초과
401invalid_api_keyAPI Key 누락 또는 잘못된 키
키 사용 시점: 받아온 source_object_key는 동일 계정의 POST /hosted-sessions에 연결하거나 POST /documents 호출에서 사용할 수 있습니다. 한 번 문서 생성에 사용되면 정식 문서 스토리지로 이동합니다. 사용되지 않은 키는 기본 24시간 후 만료되므로 발급 후 가급적 빠르게 사용하세요.
AI Document Review

AI 문서 검토

문서 생성 전에 요약·위험 요소·참여자별 의무를 검토합니다. 문서·서명 링크를 만들거나 결제·알림을 발송하지 않습니다. AI 응답은 참고 자료이며 법률 자문이나 정확성 보증이 아닙니다.

POST/internal/document-review

AI 검토는 Codex CLI를 통해 OpenAI에서 처리됩니다. 고객사는 호출 전에 문서 텍스트·제목·참여자 이름과 역할의 외부 전송을 이용자에게 안내하고 필요한 동의를 확보해야 합니다. 원본 파일과 참여자 연락처 필드는 AI 제공자에게 전송하지 않습니다.

고객사 서버에서 Authorization: Bearer sp_test_... 또는 sp_live_...로 호출합니다. 일반 호스티드 세션도 허용되지만 체험 전용 세션은 사용할 수 없습니다. 요청 형식은 multipart/form-data, 최대 파일 크기는 50MB입니다.

필드설명
file (필수)검토 파일. 현재 웹 업로드·문서 생성과 같은 PDF 사용을 권장합니다.
file_typepdf 사용, 생략하면 확장자로 추론합니다. 검토 경로는 호환 목적으로 doc, docx, hwp, hwpx도 인식하지만 추출·변환 구성에 따라 실패할 수 있습니다. 문서 생성·선업로드 API는 PDF만 지원합니다.
title검토 제목. 생략하면 파일명 사용.
participants (필수)JSON 배열 문자열. 비어 있지 않은 name이 필요합니다. role, email, phone, signing_order는 선택입니다. 앞의 유효한 참여자 최대 10명을 검토합니다.
curl -X POST https://api.moram.net/signplug/v1/internal/document-review \
  -H "Authorization: Bearer sp_test_YOUR_KEY" \
  -F "file=@contract.pdf" \
  -F "file_type=pdf" \
  -F "title=공급계약서" \
  -F 'participants=[{"name":"담당자","role":"signer"}]'

응답 — 200 OK

{
  "status": "completed",
  "generated_at": "2026-09-26T00:00:00.000Z",
  "model": "gpt-6.1-sol", "provider": "codex-cli",
  "document": { "title": "공급계약서", "file_type": "pdf", "original_filename": "contract.pdf", "extracted_characters": 1200, "truncated": false },
  "review": {
    "summary": ["계약 내용 요약"], "key_terms": ["지급 기한"],
    "risks": ["지연 책임 확인 필요"], "checklist": ["금액과 지급일 확인"],
    "participant_reviews": [{ "participant_index": 0, "name": "담당자", "role": "signer", "summary": "당사자 관점 요약", "obligations": [], "concerns": [], "questions": [] }],
    "privacy_note": "문서 내용의 일시 처리에 관한 안내"
  }
}

model은 운영 설정에 따라 달라집니다. 긴 문서는 일부만 검토하므로 document.truncated를 확인하세요. 여기서 completed는 AI 검토 완료이며 전자서명 완료가 아닙니다.

HTTP코드처리
400invalid_request / invalid_participants파일·형식·참여자 JSON 확인
401 / 403invalid_api_key / trial_scope_restricted유효한 일반 API Key 사용
413FST_REQ_FILE_TOO_LARGE50MB 이하 파일 사용
422document_text_not_available / ai_review_timeout텍스트를 읽을 수 있는 PDF 사용 또는 문서 분할
503ai_review_disabled / ai_review_unconfigured / ai_review_auth_unavailable현재 AI 검토 기능 비활성 또는 인증 확인 필요
429ai_review_busy검토 대기열이 가득 찼습니다. 잠시 후 재시도
502 / 503ai_review_provider_error / ai_review_invalid_response잠시 후 재시도. 반복되면 request_id와 함께 문의
Document List

문서 목록 조회

계정의 문서를 최신 생성순으로 조회합니다. 대시보드와 운영 모니터링에서 사용하는 API입니다.

GET /documents?page=1&limit=20&status=completed

쿼리 파라미터

쿼리타입기본값설명
pageinteger11부터 시작하는 페이지 번호
limitinteger20페이지당 항목 수. 1~100 사이
statusstring-특정 상태만 필터. 가능한 값: 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": "pdf",
      "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 문서를 구분할 수 있습니다.
Document Status

문서 상태 조회

GET /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와는 별도 흐름입니다.

GET /documents/:document_id/pdf

완료된 문서의 최종 PDF를 application/pdf로 내려받습니다. 브라우저 표시용 Content-Disposition: inline 헤더가 포함되어 있어, 그대로 <iframe>이나 새 탭에 표시할 수 있습니다. 파일을 강제로 저장하게 하려면 응답을 그대로 전달하지 말고 자체 프록시에서 attachment로 다시 내려주세요.

문서가 completed 상태가 아니거나 최종 PDF가 없으면 409 document_pdf_not_ready가 반환됩니다. 원본·중간 PDF로 대체하지 않습니다. 서명 중 미리보기는 서명 앱의 /public/signing-links/:token/pdf를 사용합니다.

GET /documents/:document_id/audit-log

문서 해시와 열람·본인확인·서명·거절·취소 이벤트를 시간순으로 반환합니다.

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": "portone_inicis_unified"
    },
    {
      "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"
    }
  ]
}
GET /documents/:document_id/audit-certificate.pdf

완료된 문서의 감사추적 인증서를 PDF로 내려받습니다. 인증서에는 문서 해시(원본·변환·최종), 참여자별 본인확인/서명 일시, IP, 본인확인 방법이 포함되어 분쟁 대응 자료로 활용할 수 있습니다. 문서가 completed 상태가 아니면 인증서가 생성되지 않습니다.

DELETE /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문서 준비 중(현재 흐름에서는 사용되지 않음). 이 상태에서는 /pdf 호출이 409
waiting모든 참여자가 아직 서명하지 않음. 서명 링크 유효
in_progress일부 참여자가 서명 완료. 순차 서명일 경우 다음 차례 참여자에게 알림 발송
completed모든 참여자 서명 완료. /pdf, /audit-certificate.pdf 모두 다운로드 가능
rejected참여자 중 한 명이 서명을 거절. 후속 참여자 알림은 발송하지 않음
expired만료 기간 초과. 서명 링크 사용 불가
cancelledDELETE 호출로 취소됨

서명자 연락처 정정

같은 서명자의 잘못 입력된 연락처를 정정하고 새 서명 요청을 발송합니다. API Key의 계정과 test/live 환경이 문서와 일치해야 합니다. 체험 세션은 사용할 수 없습니다. 다른 사람으로 서명자를 교체하는 용도가 아닙니다.

GET/documents/:document_id/details

문서 상태와 participants의 현재 email·phone·phone_corrected·email_corrected·can_correct_phone·can_send, contact_corrections 정정 이력 및 additional_emails 기존 발송 이력을 조회합니다. 서명자 ID는 participants[].id를 사용합니다. can_send는 서명 전 상태인지 나타내며 이메일 발송 한도·대기 시간 충족을 보장하지 않습니다. can_correct_phone도 발송 중 충돌까지 보장하지 않으므로 POST 결과를 확인하세요.

POST/documents/:document_id/participants/:participant_id/correct-email
curl -X POST https://api.moram.net/signplug/v1/documents/DOCUMENT_ID/participants/PARTICIPANT_ID/correct-email  \
  -H "Authorization: Bearer YOUR_API_KEY"  \
  -H "Content-Type: application/json"  \
  -d '{"email":"signer@example.com","request_id":"a0ca078e-8069-4fe1-8f22-48e0b4aa84b0","confirm_recipient":true}'

등록된 참여자 이메일도 함께 정정됩니다. 서명자당 최소 60초 간격, 최근 24시간 5회·계정 전체 20회까지 허용됩니다. 기존 추가 이메일 발송도 한도에 포함됩니다. 이메일 정정은 추가 유료 알림톡을 발송하지 않습니다.

POST/documents/:document_id/participants/:participant_id/correct-phone
curl -X POST https://api.moram.net/signplug/v1/documents/DOCUMENT_ID/participants/PARTICIPANT_ID/correct-phone  \
  -H "Authorization: Bearer YOUR_API_KEY"  \
  -H "Content-Type: application/json"  \
  -d '{"phone":"01012345678","request_id":"bf817ac0-eb39-467c-821a-19c65f2a8726","confirm_recipient":true}'

휴대폰 번호는 서명자별 서명 전 1회만 정정할 수 있으며 24시간 후에도 초기화되지 않습니다. 등록 번호와 본인인증 시 비교하는 번호가 함께 바뀝니다. 국내 휴대폰 번호를 사용합니다. 새 번호 알림톡 발송을 예약하며 등록 이메일이 있으면 새 링크도 이메일로 발송합니다. 순차 서명에서는 알림톡이 해당 서명자 차례까지 대기합니다. 발송 실패·결과 불명이어도 1회 정정은 소모됩니다.

요청과 응답

각 요청은 email 또는 phone 하나와 UUID 형식 request_id, JSON boolean confirm_recipient: true만 보냅니다. 동일 서명자의 연락처임을 확인한 뒤 호출하세요. 추가 필드는 거절합니다. 성공은 201, 동일 UUID·동일 내용의 재요청은 추가 발송 없이 200을 반환합니다. 동일 UUID를 다른 내용에 재사용하면 409 request_conflict입니다.

{"correction_id":"5ea86f77-c889-4f39-a90f-bdb89d7c84fb","email_status":"accepted","duplicate":false}

email_status: accepted는 메일 서버 접수이며 실제 수신·열람을 보장하지 않습니다. mocked는 테스트 발송, not_applicable은 등록 이메일 없음, unknown은 메일 발송 결과 불명입니다. 이는 알림톡의 수신 상태가 아닙니다. 결과 불명이나 타임아웃에는 새 UUID로 자동 재발송하지 말고 같은 UUID로 결과 확인 후 상세 이력을 조회하세요.

정정 조건과 서명 링크

만료되지 않은 waiting·in_progress 문서의 signer이며 pending·viewed·verified 상태인 참여자만 가능합니다. 완료·취소·거절·만료 문서나 이미 서명한 참여자는 정정할 수 없습니다. PDF 원문·문서 해시·서명자 이름·본인인증 필요 여부는 유지됩니다.

정정하면 해당 서명자의 기존 활성 링크와 OTP가 무효화되고 인증 상태가 초기화됩니다. 서명자는 새 링크에서 필요 시 다시 본인인증해야 합니다. 이미 보낸 메시지는 회수되지 않습니다. 원래 링크를 저장한 연동 화면도 새 링크로 진행하도록 안내하세요.

오류와 이전 API 전환

400: 입력 오류·동일 연락처. 401: 인증 실패. 403: 체험 세션 제한. 404: 문서·서명자 없음 또는 다른 계정/환경. 409 document_not_active·participant_not_pending: 정정 불가 상태. 409 contact_correction_used: 휴대폰 1회 사용. 409 notification_in_flight: 알림톡 발송 중. 429 email_send_limit: 이메일 간격 또는 24시간 한도 초과. 429에 고정 Retry-After 값은 제공하지 않으며 즉시 반복 호출하지 마세요.

이전 POST .../request-email은 연락처를 변경하지 않고 다른 주소에 발송하는 기능이었습니다. 현재는 데이터 변경 없이 409 contact_correction_refresh_required를 반환합니다. 정정에 대한 사용자 확인을 받은 뒤 correct-email로 전환하세요. 경로만 자동 치환하면 안 됩니다. 전용 연락처 정정 웹훅은 제공하지 않으며 필요하면 상세 조회와 감사 로그를 사용하세요.

Webhook

서명 진행 이벤트 수신

문서 생성, 열람, 본인확인, 서명, 완료, 거절, 만료, 취소 이벤트를 등록한 URL로 수신합니다.

POST /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_* 값이 자동 발급되어 응답에 한 번 표시됩니다. 동일 계정에 같은 URL의 활성 webhook이 이미 있으면 409 webhook_already_exists가 반환됩니다.

Webhook 비활성화

DELETE /webhooks/:webhook_id
curl -X DELETE https://api.moram.net/signplug/v1/webhooks/WEBHOOK_ID \
  -H "Authorization: Bearer sp_live_YOUR_KEY"

Webhook은 이력 보존을 위해 실제 삭제하지 않고 비활성화됩니다. 비활성화된 webhook에는 새 이벤트가 발송되지 않습니다.

수신 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참여자가 본인확인 완료
document.signed참여자 한 명이 서명 완료. data.event_data.method는 canvas, image_upload, generated_stamp 중 하나입니다.
document.completed모든 참여자 서명 완료
document.rejected참여자가 서명 거절
document.expired서명 링크 만료
document.reminded리마인더 발송
document.cancelled문서 취소
Webhook은 기본 10초 안에 2xx 응답을 받아야 성공 처리됩니다. 실패 시 60초, 5분, 30분, 2시간 간격으로 재시도하고, 마지막 시도까지 실패하면 dead letter 상태가 됩니다.

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
Operations

운영 · 과금 · 키 관리

테스트 키는 카드 없이 사용할 수 있습니다. 라이브 키는 활성 결제수단 등록 후 발급·재발급할 수 있으며, 미등록 상태에서 라이브 문서 생성을 시도하면 402 payment_method_required가 반환됩니다. 결제수단이 있어도 문서 생성 시점의 카드 승인이 거절되면 402 card_declined가 반환되고 문서는 생성되지 않습니다.

API Key 관리

엔드포인트용도
POST /api-keys누락된 API Key 발급. 테스트 키는 카드 없이 발급되며, 라이브 키는 활성 결제수단 등록 후 발급
GET /api-keys계정의 test/live API Key 목록 조회. 키 원문은 재표시되지 않으며 key_prefix(앞 7~12자)로 구분합니다.
POST /api-keys/:api_key_id/rotate기존 키를 비활성화하고 같은 환경의 새 키를 발급. 라이브 키 재발급은 활성 결제수단 필요
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)
PATCH /billing/method/card-display기존 국내 카드의 표시용 끝 4자리 저장
GET /billing/usage?year=YYYY&month=M해당 월의 문서 수, API/웹 분리 카운트, 청구 금액, 청구 상태 조회
GET /billing/outstanding아직 청구되지 않은 누적 사용량과 금액. 문서마다 서명 시작 때 결제되므로 항상 0입니다(이전 버전 호환용)
GET /billing/web-entitlement결제 수단 등록 필요 여부와 웹 문서 사용 건수
GET /billing/configKG이니시스/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": "portone",
  "card_company": "하나카드",
  "card_number_masked": "****-****-****-1234",
  "customer_key": "sp_customer...",
  "subscription_id": null,
  "is_active": true
}
결제 수단 등록: 카드 등록·교체는 보안상 API로 직접 처리할 수 없으며 SignPlug 대시보드의 KG이니시스/Paddle 결제창에서 진행합니다. 라이브 키로 과금 대상 문서를 생성하려면 활성 결제수단이 필요합니다.
과금 정책: 웹/API 생성 문서는 기본 300원(VAT 포함)입니다. 문서 1건에는 서명자 2명 · 알림톡·이메일 안내 · 본인인증이 포함되고, 3명째 서명자부터 1명당 100원(VAT 포함)이 추가됩니다. 결제는 문서 1건 단위로 이루어집니다. POST /documents 처리 중 등록된 결제수단에 표시 금액 그대로(VAT 포함) 승인되며, 승인이 거절되면 문서가 생성되지 않고 알림도 발송되지 않습니다. 월별 합산 후불 청구는 하지 않습니다. test 환경은 과금하지 않고 실제 알림톡도 발송하지 않습니다. live 환경의 서명 요청·완료 알림톡은 signer 역할 참여자에게만 비동기로 발송되며, 통신사·카카오 수신 상태에 따라 전달되지 않을 수 있습니다. viewer·approver 역할에는 이메일만 발송되고 과금 대상도 아닙니다. 이메일 안내는 모든 참여자에게 유지됩니다.
Error Codes

오류 코드

모든 오류는 동일한 형식으로 반환됩니다.

// 오류 응답 형식
{
  "error": "invalid_request",
  "message": "The request body is invalid.",
  "details": null,
  "request_id": "req-13m",
  "responsibility": "client",
  "retryable": false,
  "field_errors": [
    {
      "field": "options.reminder_intervals_hours",
      "message": "This field must be an array. Use [] when no reminders are needed."
    }
  ]
}

responsibility가 client이면 요청 값을 수정해야 하고, signplug이면 request_id와 함께 문의해 주세요. 공개 응답에는 내부 스택·저장소·DB 등 서비스 구조 정보가 포함되지 않습니다.

HTTP코드원인
400invalid_request필수 필드 누락, 잘못된 파일 형식, 서명자 10명 초과, 잘못된 Webhook URL 등
400recipient_consent_required문서 생성 전 수신자의 전자문서 수신 및 전자서명 진행 사전 동의 확인이 누락됨
400identity_verification_required폐기된 오류: require_sms_verification=false 및 참여자별 인증 생략을 허용합니다.
400notification_required알림톡·이메일 안내 필수 정책상 send_notification을 false로 보낼 수 없음
400signer_phone_required본인인증을 사용하는 서명자의 휴대폰 번호 누락
400participant_contact_required참여자의 이메일과 휴대폰 번호가 모두 누락됨
400source_not_availablesource_object_key를 현재 요청에서 사용할 수 없음. 다시 업로드 후 재시도
400agreement_required서명 제출 시 약관 동의 누락
401missing_authorizationAuthorization 헤더 없음
401invalid_authorizationBearer 형식이 아님
401invalid_api_keyAPI Key가 잘못되었거나 비활성화됨
402payment_method_requiredlive 문서 생성에 필요한 결제 수단 없음. 대시보드에서 카드 등록 후 재시도
402card_declined문서 생성 시점의 카드 승인 거절. 문서는 생성되지 않음. details.reason 에 카드사 사유, details.temporary 가 true 면 같은 카드로 나중에 재시도 가능(한도 초과·점검 등), false 면 다른 카드 등록 필요
404document_not_found문서 ID가 없거나, 다른 계정의 문서라서 권한이 없음
404signing_link_not_found서명 토큰을 찾을 수 없음. 잘못된 토큰이거나 이미 폐기됨
409document_pdf_not_ready변환 또는 최종 PDF 생성이 아직 완료되지 않음. 잠시 후 재시도
409participant_not_verified본인확인 전 서명 제출. 인증 사용 설정일 때 /identity-verification/prepare 및 /identity-verification/confirm 흐름 완료 필요
409invalid_state완료/만료/취소된 문서에 DELETE를 호출하는 등 상태가 맞지 않음
409webhook_already_exists동일 계정에 같은 URL의 활성 webhook이 이미 등록됨
410document_expired서명 가능 기간 만료
413FST_REQ_FILE_TOO_LARGE업로드 파일이 50MB 한도를 초과. multipart 응답 헤더로 반환
429test_quota_exhausted테스트 환경 본인확인 누적 시도 한도 초과. 한도 증가는 contact@moram.net으로 문의. details.sent·details.limit으로 현재 사용량 확인 가능
500internal_server_errorSignPlug 서비스 오류. 응답의 request_id와 함께 문의
재시도 가이드: 409 document_pdf_not_ready처럼 일시적인 상태 오류는 지수 백오프(예: 1s → 2s → 4s)로 재시도하면 됩니다. 4xx 오류 중 invalid_request, invalid_api_key, document_not_found 등은 클라이언트 측 입력 문제이므로 재시도하지 마세요.
Integration Tips

자주 묻는 질문 · 통합 팁

PDF 좌표를 어떻게 알 수 있나요?

웹 서명 도구에서 마우스로 필드를 배치하면 정확한 x, y, width, height 값을 확인할 수 있습니다. 이 값을 그대로 API에 넘기세요.

순차 서명 vs 동시 서명

signing_order를 서로 다르게 지정하면 순차 서명, 동일하게 지정하면 동시 서명입니다. 예: [1, 1, 2]이면 앞의 두 명이 함께 진행하고, 두 명 모두 서명한 뒤 세 번째 사람이 진행합니다. 이메일은 생성 시 전원에게 발송하고, live 알림톡은 현재 순서 그룹의 서명자에게 비동기로 발송합니다. 리마인더 이메일도 현재 순서 그룹이 대상입니다.

파일이 크거나 base64가 불편할 때

POST /internal/source-upload로 먼저 파일을 업로드하고, 받은 source_object_key를 POST /hosted-sessions 또는 POST /documents에 전달하면 base64 인코딩 없이 대용량 파일을 처리할 수 있습니다.

Test 환경에서 본인확인 확인

Test 키로 발급된 문서도 KG이니시스 본인확인 창을 호출합니다. KG이니시스 통합본인인증은 참여자의 name, phone, email 정보를 사용하므로 정확한 정보를 전달해 주세요.

Webhook을 받지 못할 때

서버가 설정된 제한 시간 안에 2xx를 응답하지 않으면 재시도됩니다. 운영 환경에서는 https:// URL만 등록할 수 있으므로 로컬 개발 시에는 ngrok이나 유사 터널링 도구로 임시 URL을 생성하세요.