개발자
REST API · 웹훅
ERP·인사·CRM 같은 사내 시스템에서 계약서를 만들어 보내고, 서명·체결 소식을 바로 받아요. API 키는 회사 관리 › API·웹훅에서 소유자·관리자가 만들어요.
인증
모든 요청의 Authorization 헤더에 API 키를 넣어요. 키는 만들 때 한 번만 보여요. 1분에 120번까지 부를 수 있어요.
Authorization: Bearer sp_live_xxxxxxxxxxxxxxxxxxxxxxxx
주소
| POST | /v1/envelopes | 파일(multipart file) + data(JSON)로 문서를 만들고, send:true 면 바로 보내요. |
| GET | /v1/envelopes | 보낸 문서 목록(status·q·limit). |
| GET | /v1/envelopes/{id} | 상태·서명인·자리·체결본 지문. |
| POST | /v1/envelopes/{id}/send | 초안을 보내요. |
| POST | /v1/envelopes/{id}/void | 서명 요청을 취소해요(reason). |
| GET | /v1/envelopes/{id}/events | 감사 기록(열람·동의·서명·발송). |
| GET | /v1/envelopes/{id}/final.pdf | 체결본 PDF(인증서·전자 봉인 포함). |
예: 계약서 보내기
받는 사람만 주면 AI가 서명 자리를 찾아 배정해요. 자리를 직접 정하려면 fields에 쪽 비율 좌표(0~1)를 넣어요.
curl https://signpen.ai/v1/envelopes \
-H "Authorization: Bearer $SIGNPEN_KEY" \
-F file=@contract.pdf \
-F 'data={
"title": "용역 계약서",
"message": "검토 후 서명 부탁드립니다.",
"expire_days": 14,
"remind_days": 3,
"recipients": [
{ "name": "김민지", "email": "minji@example.com", "party": "갑" },
{ "name": "Alex Kim", "email": "alex@example.com", "party": "을", "lang": "en", "auth_method": "email_otp" }
],
"send": true
}'
응답의 data.status가 sent 면 메일이 나간 거예요. 요금은 보낼 때 회사 크레딧에서 차감돼요.
받는 사람 항목
| name, email | 필수 |
| role | signer(기본) 또는 cc(참조 — 사본만) |
| party | 문서 속 호칭(갑·을·근로자…). AI가 찾은 자리를 이 사람에게 배정해요. |
| order | routing이 sequential 일 때 순서 |
| lang | ko 또는 en — 메일·서명 화면 언어 |
| auth_method | none · code(access_code 필요) · email_otp |
웹훅
회사 관리 › API·웹훅에서 https 주소를 등록하면 아래 사건마다 JSON을 POST 해요. 2xx가 아니면 1분·5분·30분·2시간·6시간 뒤에 다시 보내요.
| envelope.sent | 보냄 |
| recipient.viewed | 받는 사람이 열어 봄 |
| recipient.signed | 한 사람이 서명 |
| envelope.declined | 거절 — 요청 멈춤 |
| recipient.delegated | 서명 위임 |
| envelope.completed | 모두 서명 — 체결본 준비됨 |
| envelope.voided | 보낸 사람이 취소 |
| envelope.expired | 기한 지남 |
진짜 싸인펜이 보낸 것인지 요청 헤더의 SignPen-Signature로 확인하세요. t는 보낸 시각(초), v1은 「t.본문」을 웹훅 비밀로 HMAC-SHA256 한 값이에요. 5분보다 오래된 요청은 버리세요.
// Node.js
import crypto from 'node:crypto';
const [t, v1] = req.get('SignPen-Signature').split(',').map((p) => p.split('=')[1]);
const want = crypto.createHmac('sha256', process.env.SIGNPEN_WEBHOOK_SECRET).update(`${t}.${rawBody}`).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(want)) && Date.now() / 1000 - Number(t) < 300;
{
"id": "evt_…",
"type": "envelope.completed",
"created_at": "2026-10-18T09:12:03.120Z",
"data": { "envelope_id": "…", "title": "용역 계약서", "status": "completed", "sent_at": "…", "completed_at": "…", "final_sha256": "…" }
}
오류
오류는 HTTP 상태와 함께 { "error": { "code", "message" } }로 와요. 401 키 오류 · 404 없는 문서 · 422 보낼 준비 안 됨(problems에 이유) · 429 너무 잦은 요청.