Signing Flow Options
Configure how documents are presented to signers with email delivery, embedded signing, and routing options.
Delivery Methods
| Method | Use Case | User Experience |
|---|---|---|
| External parties, async | Signer receives email, clicks link | |
| Embedded | In-app signing | Signer signs within your application |
| Hybrid | Mix of internal/external | Some embedded, some via email |
Email Signing
Flow Diagram
Basic Email Signing
- cURL
- JavaScript
- Python
curl -X POST "https://api.propper.ai/restapi/v2.1/accounts/{accountId}/envelopes" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"emailSubject": "Please sign: Service Agreement",
"emailBlurb": "Please review and sign this document at your earliest convenience.",
"status": "sent",
"documents": [{
"documentId": "1",
"name": "Agreement.pdf",
"documentBase64": "JVBERi0xLjQK..."
}],
"recipients": {
"signers": [{
"email": "client@example.com",
"name": "John Smith",
"recipientId": "1",
"routingOrder": "1"
}]
}
}'
const sendForSignature = async (accountId, token, documentBase64) => {
const response = await fetch(
`https://api.propper.ai/restapi/v2.1/accounts/${accountId}/envelopes`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
emailSubject: 'Please sign: Service Agreement',
emailBlurb: 'Please review and sign this document at your earliest convenience.',
status: 'sent',
documents: [{
documentId: '1',
name: 'Agreement.pdf',
documentBase64,
}],
recipients: {
signers: [{
email: 'client@example.com',
name: 'John Smith',
recipientId: '1',
routingOrder: '1',
}],
},
}),
}
);
return response.json();
};
def send_for_signature(account_id: str, token: str, document_base64: str) -> dict:
response = requests.post(
f"https://api.propper.ai/restapi/v2.1/accounts/{account_id}/envelopes",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
json={
"emailSubject": "Please sign: Service Agreement",
"emailBlurb": "Please review and sign this document at your earliest convenience.",
"status": "sent",
"documents": [{
"documentId": "1",
"name": "Agreement.pdf",
"documentBase64": document_base64,
}],
"recipients": {
"signers": [{
"email": "client@example.com",
"name": "John Smith",
"recipientId": "1",
"routingOrder": "1",
}],
},
},
)
response.raise_for_status()
return response.json()
Customizing Email Content
{
"emailSubject": "Action Required: {{documentName}} needs your signature",
"emailBlurb": "Dear {{recipientName}},\n\nPlease review and sign the attached document.\n\nThis link will expire in 7 days.\n\nBest regards,\nThe Acme Team",
"brandId": "brand_abc123"
}
Email Notifications
Configure notification preferences per recipient:
{
"recipients": {
"signers": [{
"email": "signer@example.com",
"name": "John Smith",
"recipientId": "1",
"emailNotification": {
"emailSubject": "Custom subject for this recipient",
"emailBody": "Custom body text",
"supportedLanguage": "en"
}
}]
}
}
Embedded Signing
For embedded signing, see the dedicated Embedded Signing Guide.
Quick summary:
Key requirement: Set clientUserId on recipients to enable embedded signing.
Signing Order
Sequential Signing
Signers must sign in order. Signer 2 won't receive notification until Signer 1 completes.
{
"recipients": {
"signers": [
{
"email": "ceo@company.com",
"name": "CEO",
"recipientId": "1",
"routingOrder": "1"
},
{
"email": "cfo@company.com",
"name": "CFO",
"recipientId": "2",
"routingOrder": "2"
},
{
"email": "legal@company.com",
"name": "Legal",
"recipientId": "3",
"routingOrder": "3"
}
]
}
}
Parallel Signing
Multiple signers can sign simultaneously by using the same routingOrder.
{
"recipients": {
"signers": [
{
"email": "party-a@example.com",
"name": "Party A",
"recipientId": "1",
"routingOrder": "1"
},
{
"email": "party-b@example.com",
"name": "Party B",
"recipientId": "2",
"routingOrder": "1"
},
{
"email": "notary@example.com",
"name": "Notary",
"recipientId": "3",
"routingOrder": "2"
}
]
}
}
Mixed Routing
Combine sequential and parallel:
{
"recipients": {
"signers": [
{ "email": "employee@company.com", "routingOrder": "1" },
{ "email": "manager@company.com", "routingOrder": "2" },
{ "email": "hr@company.com", "routingOrder": "2" },
{ "email": "legal@company.com", "routingOrder": "3" }
]
}
}
Flow: Employee → (Manager AND HR in parallel) → Legal
Recipient Types
| Type | Description | Can Sign | Receives Copy |
|---|---|---|---|
signers | Must sign the document | Yes | Yes |
carbonCopies | Receives copy when complete | No | Yes |
certifiedDeliveries | Must confirm receipt | No | Yes |
viewers | Can view but not sign | No | Optional |
Adding CC Recipients
{
"recipients": {
"signers": [{
"email": "signer@example.com",
"name": "Main Signer",
"recipientId": "1",
"routingOrder": "1"
}],
"carbonCopies": [{
"email": "legal@company.com",
"name": "Legal Department",
"recipientId": "2",
"routingOrder": "2"
}]
}
}
Reminders
Automatic Reminders
Configure automatic reminders in the envelope:
{
"notification": {
"useAccountDefaults": false,
"reminders": {
"reminderEnabled": true,
"reminderDelay": "2",
"reminderFrequency": "2"
},
"expirations": {
"expireEnabled": true,
"expireAfter": "120",
"expireWarn": "3"
}
}
}
| Field | Description |
|---|---|
reminderDelay | Days before first reminder |
reminderFrequency | Days between reminders |
expireAfter | Days until expiration |
expireWarn | Days before expiry to warn |
Manual Reminders
- cURL
- JavaScript
- Python
curl -X POST "https://api.propper.ai/restapi/v2.1/accounts/{accountId}/envelopes/{envelopeId}/recipients/{recipientId}/reminder" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"reminderMessage": "Friendly reminder: Please sign the document at your earliest convenience."
}'
const sendReminder = async (accountId, envelopeId, recipientId, token) => {
const response = await fetch(
`https://api.propper.ai/restapi/v2.1/accounts/${accountId}/envelopes/${envelopeId}/recipients/${recipientId}/reminder`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
reminderMessage: 'Friendly reminder: Please sign the document at your earliest convenience.',
}),
}
);
return response.ok;
};
def send_reminder(account_id: str, envelope_id: str, recipient_id: str, token: str) -> bool:
response = requests.post(
f"https://api.propper.ai/restapi/v2.1/accounts/{account_id}/envelopes/{envelope_id}/recipients/{recipient_id}/reminder",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
json={
"reminderMessage": "Friendly reminder: Please sign the document at your earliest convenience.",
},
)
return response.ok
Document Expiration
Setting Expiration
{
"emailSubject": "Time-sensitive: Contract expires soon",
"status": "sent",
"notification": {
"expirations": {
"expireEnabled": true,
"expireAfter": "7",
"expireWarn": "2"
}
}
}
Handling Expired Documents
When a document expires:
- Webhook
document.expiredis triggered - Signers can no longer access the document
- You can create a new envelope to resend
// Handle expiration webhook
if (event.type === 'document.expired') {
const { envelopeId } = event.data.document;
// Option 1: Create new envelope with same content
const newEnvelope = await resendEnvelope(envelopeId);
// Option 2: Notify your team
await notifyTeam(`Envelope ${envelopeId} has expired`);
}
Voiding Envelopes
Cancel an envelope that's been sent:
- cURL
- JavaScript
- Python
curl -X PUT "https://api.propper.ai/restapi/v2.1/accounts/{accountId}/envelopes/{envelopeId}" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"status": "voided",
"voidedReason": "Contract terms changed - sending updated version"
}'
const voidEnvelope = async (accountId, envelopeId, reason, token) => {
const response = await fetch(
`https://api.propper.ai/restapi/v2.1/accounts/${accountId}/envelopes/${envelopeId}`,
{
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
status: 'voided',
voidedReason: reason,
}),
}
);
return response.json();
};
await voidEnvelope(accountId, envelopeId, 'Contract terms changed', token);
def void_envelope(account_id: str, envelope_id: str, reason: str, token: str) -> dict:
response = requests.put(
f"https://api.propper.ai/restapi/v2.1/accounts/{account_id}/envelopes/{envelope_id}",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
json={
"status": "voided",
"voidedReason": reason,
},
)
response.raise_for_status()
return response.json()
void_envelope(account_id, envelope_id, "Contract terms changed", token)
Voiding is irreversible. Recipients will be notified that the document has been voided.
Resending Envelopes
Resend signing notifications to recipients who haven't signed:
- cURL
- JavaScript
- Python
curl -X PUT "https://api.propper.ai/restapi/v2.1/accounts/{accountId}/envelopes/{envelopeId}?resend_envelope=true" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
const resendEnvelope = async (accountId, envelopeId, token) => {
const response = await fetch(
`https://api.propper.ai/restapi/v2.1/accounts/${accountId}/envelopes/${envelopeId}?resend_envelope=true`,
{
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
}
);
return response.json();
};
def resend_envelope(account_id: str, envelope_id: str, token: str) -> dict:
response = requests.put(
f"https://api.propper.ai/restapi/v2.1/accounts/{account_id}/envelopes/{envelope_id}",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
params={"resend_envelope": "true"},
json={},
)
response.raise_for_status()
return response.json()
Status Transitions
| Status | Description | Can Modify |
|---|---|---|
created | Draft envelope | Yes |
sent | Sent to recipients | Limited |
delivered | Viewed by recipient | No |
completed | All signatures collected | No |
declined | Recipient refused | No |
voided | Cancelled by sender | No |
Next Steps
- Embedded Signing - Sign within your app
- Webhooks - Real-time status notifications
- Error Handling - Handle signing errors