Callback Overview
After configuring the CallbackUrl on the AppId settings page in the console, the server will send a POST request to the CallbackUrl whenever an envelope status changes.
> ⚠️ Note: All callbacks are POST requests. If you receive a GET request, please check whether your CallbackUrl has an HTTP-to-HTTPS 301/302 redirect.
Configuration
- 1. Log in to the console, go to 'App Management' → select the target AppId
- 2. In the 'Callback Configuration' section, fill in the CallbackUrl (must be a publicly accessible HTTPS address)
- 3. After saving, the system generates a 32-character CallbackKey (Base62) for encrypting/decrypting callback data
- 4. Keep the CallbackKey safe. If lost, regenerate it (the old key is invalidated immediately)
⚠️CallbackUrl must use HTTPS
CallbackUrl must be publicly accessible (no internal IPs or localhost)
Each AppId can have multiple CallbackUrls, each with its own CallbackKey
Different CallbackUrls use independent CallbackKeys
CallbackUrl must be publicly accessible (no internal IPs or localhost)
Each AppId can have multiple CallbackUrls, each with its own CallbackKey
Different CallbackUrls use independent CallbackKeys
Data Format & Encryption
Data Format & Encryption
{
"encrypt": "dGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIGV4YW1wbGU..."
}AES-256-GCM
Key
32 bytes (256 bits)
Nonce
12 bytes (96 bits, randomly generated)
Ciphertext Format
Base64( nonce[12 bytes] + GCM_ciphertext + GCM_tag[16 bytes] )
Decryption Steps
- 1. Extract the encrypt field from the request body
- 2. Base64-decode to get raw bytes
- 3. First 12 bytes = nonce, remainder = GCM ciphertext (includes 16-byte auth tag)
- 4. Use CallbackKey (32 bytes, UTF-8) to create AES-256-GCM decryptor
- 5. GCM.Open(nonce, ciphertext) to decrypt to JSON plaintext
- 6. Deserialize JSON to EnvelopeCallbackPayload
Delivery Policy
HTTP Timeout
5 seconds (HTTP request timeout)
Success Condition
Client returns HTTP 200 within 5 seconds
Max Retries
36
Retry Schedule
1s, 2s, 3s, 4s, 5s → 10s, 15s, 20s, 25s, 30s, 35s, 40s, 45s, 50s, 55s → 1min, 2min, 3min, 4min, 5min, 6min, 7min, 8min, 9min, 10min → 15min, 25min, 35min, 45min, 55min → 1h, 2h, 3h, 4h, 5h, 6h
Ordering
Callbacks for the same envelope + integration are delivered in event order
ℹ️Retry intervals grow with attempt count. Once HTTP 200 is received, the platform stops re-sending this callback. After 36 failed retries, the platform considers the message undeliverable and discards it.
Event Types
envelope_sentevent
Envelope sent, signing process started — In sequential mode, only the first batch of signers are notified; in parallel mode, all signers are notified simultaneously.
envelope_completedevent
Envelope completed, all signers have signed — Triggered when the last signer completes. Use 'Get Envelope Detail' API to retrieve signed documents.
envelope_voidedevent
Envelope voided (manually recalled) — Triggered when the sender recalls the envelope via console or API.
envelope_declinedevent
Envelope declined by a signer — Triggered when a signer declines. Recipients includes the declining signer and reason.
envelope_expiredevent
Envelope expired, deadline passed with unsigned signers — Triggered when the signing deadline expires with pending signers. Status becomes voided. Recipients includes all unsigned signers.
recipient_signedevent
A signer has completed signing — Triggered after a signer submits all signing fields. Envelope status remains sent while other signers are pending.
recipient_viewedevent
A recipient has viewed the envelope — Triggered on first view by a signer or CC recipient. Only fires once per recipient.
Payload Structure
EnvelopeCallbackPayload
EventTypestring
Event type. envelope_sent = envelope sent, signing workflow started (in sequential signing, only the first batch of signers are notified); envelope_completed = envelope completed (all signers have signed); envelope_voided = envelope voided (actively recalled); envelope_declined = envelope declined (triggered when any signer declines), Recipients contains that signer's info; envelope_expired = envelope expired (deadline reached with unsigned signers, auto-voided, CurrentEnvelopeStatus=voided), Recipients contains all unsigned signers; recipient_signed = a signer completed signing; recipient_viewed = a recipient (signer or carbon copy) viewed the envelope
envelope_sentenvelope_completedenvelope_voidedenvelope_declinedenvelope_expiredrecipient_signedrecipient_viewed
EnvelopeIdstring
Envelope ID
OccurredAtstring
Time when the envelope status changed, RFC3339 format, e.g., 2024-01-15T10:45:00Z
PreviousEnvelopeStatusstring
Envelope status before the change. sent = signing in progress (includes sent/delivered/viewed/partially signed)
draftsentcompletedvoideddeclined
CurrentEnvelopeStatusstring
Envelope status after the change. sent = signing in progress (includes sent/delivered/viewed/partially signed)
draftsentcompletedvoideddeclined
VoidReasonstring
Void reason (has value for envelope_voided / envelope_expired events)
Recipientsarray<object>
Recipients involved in this event. recipient_* events contain a single recipient; envelope_expired contains all unsigned signers; other envelope_* events have an empty list
RecipientIdstring
Recipient ID
Namestring
Recipient name
Emailstring
Recipient email (masked, e.g., z***@example.com)
RecipientTypestring
Recipient type. signer; carbonCopy.
signercarbonCopy
SigningOrderinteger
Signing order (0 when sequential signing is not set)
PreviousStatusstring
Recipient status before the change
CurrentStatusstring
Recipient status after the change
OperatedAtstring
Operation time in RFC3339 format, e.g., 2024-01-15T11:20:00Z
DeclineReasonstring
Decline reason (only has value for envelope_declined events)
Payload Examples
envelope_sent — Envelope sent, signing process started
{
"EventType": "envelope_sent",
"EnvelopeId": "SGEVa1b2c3d4e5f6",
"OccurredAt": "2024-01-15T10:30:00Z",
"PreviousEnvelopeStatus": "draft",
"CurrentEnvelopeStatus": "sent",
"VoidReason": "",
"Recipients": []
}envelope_completed — Envelope completed, all signers have signed
{
"EventType": "envelope_completed",
"EnvelopeId": "SGEVa1b2c3d4e5f6",
"OccurredAt": "2024-01-15T14:00:00Z",
"PreviousEnvelopeStatus": "sent",
"CurrentEnvelopeStatus": "completed",
"VoidReason": "",
"Recipients": []
}envelope_voided — Envelope voided (manually recalled)
{
"EventType": "envelope_voided",
"EnvelopeId": "SGEVa1b2c3d4e5f6",
"OccurredAt": "2024-01-16T09:00:00Z",
"PreviousEnvelopeStatus": "sent",
"CurrentEnvelopeStatus": "voided",
"VoidReason": "合同条款需要修改",
"Recipients": []
}envelope_declined — Envelope declined by a signer
{
"EventType": "envelope_declined",
"EnvelopeId": "SGEVa1b2c3d4e5f6",
"OccurredAt": "2024-01-15T15:30:00Z",
"PreviousEnvelopeStatus": "sent",
"CurrentEnvelopeStatus": "declined",
"VoidReason": "",
"Recipients": [
{
"RecipientId": "SGRCb1c2d3e4f5",
"Name": "李四",
"Email": "l***@example.com",
"SigningOrder": 2,
"PreviousStatus": "viewed",
"CurrentStatus": "declined",
"OperatedAt": "2024-01-15T15:30:00Z",
"DeclineReason": "合同金额有误,需要修改"
}
]
}envelope_expired — Envelope expired, deadline passed with unsigned signers
{
"EventType": "envelope_expired",
"EnvelopeId": "SGEVa1b2c3d4e5f6",
"OccurredAt": "2024-02-15T23:59:59Z",
"PreviousEnvelopeStatus": "sent",
"CurrentEnvelopeStatus": "voided",
"VoidReason": "签署截止时间已到期",
"Recipients": [
{
"RecipientId": "SGRCc2d3e4f5g6",
"Name": "王五",
"Email": "w***@example.com",
"SigningOrder": 2,
"PreviousStatus": "sent",
"CurrentStatus": "voided",
"OperatedAt": "2024-02-15T23:59:59Z",
"DeclineReason": ""
}
]
}recipient_signed — A signer has completed signing
{
"EventType": "recipient_signed",
"EnvelopeId": "SGEVa1b2c3d4e5f6",
"OccurredAt": "2024-01-15T10:45:00Z",
"PreviousEnvelopeStatus": "sent",
"CurrentEnvelopeStatus": "sent",
"VoidReason": "",
"Recipients": [
{
"RecipientId": "SGRCb1c2d3e4f5",
"Name": "张三",
"Email": "z***@example.com",
"SigningOrder": 1,
"PreviousStatus": "viewed",
"CurrentStatus": "signed",
"OperatedAt": "2024-01-15T10:45:00Z",
"DeclineReason": ""
}
]
}recipient_viewed — A recipient has viewed the envelope
{
"EventType": "recipient_viewed",
"EnvelopeId": "SGEVa1b2c3d4e5f6",
"OccurredAt": "2024-01-15T10:35:00Z",
"PreviousEnvelopeStatus": "sent",
"CurrentEnvelopeStatus": "sent",
"VoidReason": "",
"Recipients": [
{
"RecipientId": "SGRCb1c2d3e4f5",
"Name": "张三",
"Email": "z***@example.com",
"SigningOrder": 1,
"PreviousStatus": "delivered",
"CurrentStatus": "viewed",
"OperatedAt": "2024-01-15T10:35:00Z",
"DeclineReason": ""
}
]
}Best Practices
💡
- Keep CallbackKey secure; use environment variables or a secrets manager instead of hardcoding
- Keep callback handling lightweight; return HTTP 200 within 5 seconds; process complex logic asynchronously
- Implement idempotent handling, as the same event may be delivered more than once in edge cases
- Log all callback events for troubleshooting
- Use EnvelopeId + EventType + OccurredAt to determine event uniqueness