Migrating Your Integration from Docusign
Use this page if you are moving your integration code. This guide helps you point an existing Docusign eSignature integration at the Propper Sign API with minimal code changes.
Moving your signed envelopes rather than your code? That is a different job — see Migrating Your Data from Docusign.
What: Move your e-signature workflows from Docusign to Propper Sign.
Why it's straightforward: Propper Sign offers backwards-compatible API endpoints for common Docusign operations, so migrations require minimal code changes—primarily updating the base URL and authentication.
Two options: Switch everything at once (hard cutover) or run both systems in parallel during transition.
Propper Sign offers backwards-compatible API endpoints for core Docusign v2.1 signing workflows, including envelope creation, embedded signing, recipient routing, document retrieval, and status tracking. See Feature Parity for detailed compatibility information.
- Migrating Your Data from Docusign - Use Docusign Connect webhooks to import historical/completed envelopes and keep status synced
- Sign Webhooks - Propper → your backend event notifications
- Embedded Signing - Recipient view setup and best practices
Who This Guide Is For
This guide is for you if:
- You currently call Docusign eSignature REST API v2.1 from your backend and want to switch to Propper
- You want minimal code changes and a predictable cutover process
Overview
Most migrations involve three changes:
- Base URL — Change where API requests are sent (from Docusign servers to Propper servers)
- Authentication — Change how your system proves its identity (from Docusign's JWT tokens to Propper's OAuth client credentials)
- Webhook strategy — Decide how you'll receive notifications about signing events
Do not hardcode na1.docusign.net or any specific Docusign region. Docusign recommends retrieving the correct base_uri via the UserInfo endpoint ↗ (/oauth/userinfo). The base URI depends on where your Docusign account is hosted.
Migration Paths
Choose the approach that fits your situation:
Option A: Hard Cutover
Switch base URL and auth in one deployment. Best for:
- Simple integrations with few endpoints
- No in-flight Docusign envelopes to track
- Teams comfortable with a clean break
Option B: Parallel Operation + Sync
Keep Docusign running during transition, import into Propper via Connect. Best for:
- Complex integrations needing gradual migration
- Existing Docusign envelopes that need to complete
- Teams wanting to test Propper before full cutover
See Migration Strategy with Docusign Connect for the parallel approach.
Quick Migration Checklist
- Update API base URL to Propper
- Replace Docusign auth with Propper OAuth (client credentials)
- Swap Docusign SDK usage for:
- Direct HTTP calls (recommended), or
- A thin compatibility wrapper
- Update webhook strategy:
- If you used Docusign Connect, decide whether to keep it during transition
- If you need Propper outbound events, configure Propper Webhooks
- Test core flows end-to-end (see Testing Checklist)
- Review Feature Parity (especially tabs and templates)
- Update error handling for Propper error codes
API Endpoint Mapping
Base URL
| Environment | Docusign | Propper |
|---|---|---|
| Production | https://{base_uri} (from UserInfo ↗) | https://api.propper.ai |
Docusign base URI is account-region specific. Always fetch it from /oauth/userinfo rather than hardcoding a region like na1, eu, or au.
Endpoint Paths
Propper uses the same path structure as Docusign for core eSignature v2.1 envelope operations. Simply replace the base URL:
| Operation | Path |
|---|---|
| Create envelope | POST /accounts/{accountId}/envelopes |
| Get envelope | GET /accounts/{accountId}/envelopes/{envelopeId} |
| List envelopes | GET /accounts/{accountId}/envelopes |
| Update envelope | PUT /accounts/{accountId}/envelopes/{envelopeId} |
| Get recipients | GET /accounts/{accountId}/envelopes/{envelopeId}/recipients |
| Recipient view | POST /accounts/{accountId}/envelopes/{envelopeId}/views/recipient |
| Sender view | POST /accounts/{accountId}/envelopes/{envelopeId}/views/sender |
| List documents | GET /accounts/{accountId}/envelopes/{envelopeId}/documents |
| Download document | GET /accounts/{accountId}/envelopes/{envelopeId}/documents/{documentId} |
All paths are prefixed with /restapi/v2.1. See the Docusign Envelopes API reference ↗ for detailed request/response schemas.
In Propper, the {accountId} path parameter is your Propper Organization ID. Find this in your Settings.
Known Differences
| Field/Feature | Docusign | Propper | Notes |
|---|---|---|---|
| Account ID | Docusign account GUID | Propper org ID | Different value, same path position |
Recipient view returnUrl | Required | Required | Same field, same behavior |
Recipient view clientUserId | Required for embedded | Required for embedded | Same field, same behavior |
Authentication Migration
Docusign JWT → Propper OAuth
Docusign JWT grant ↗ is an OAuth flow for service integrations that commonly uses the signature and impersonation scopes (requiring user consent).
Propper uses OAuth client credentials, which is simpler—no user consent flow required:
- Docusign JWT (Before)
- Propper OAuth (After)
// Docusign JWT authentication
const docusign = require('docusign-esign');
const apiClient = new docusign.ApiClient();
// Note: In production, get base_uri from /oauth/userinfo
apiClient.setBasePath('https://na1.docusign.net/restapi');
const results = await apiClient.requestJWTUserToken(
integrationKey,
userId,
'signature',
privateKey,
3600,
);
const accessToken = results.body.access_token;
// Propper OAuth client credentials
const getAccessToken = async () => {
const response = await fetch('https://auth.propper.ai/oauth2/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'client_credentials',
client_id: process.env.PROPPER_CLIENT_ID,
client_secret: process.env.PROPPER_CLIENT_SECRET,
scope: 'sign:read sign:write',
}),
});
if (!response.ok) {
throw new Error('Failed to get access token');
}
const { access_token, expires_in } = await response.json();
return { accessToken: access_token, expiresIn: expires_in };
};
Scope Mapping
| Docusign Scope | Propper Scope | Notes |
|---|---|---|
signature | sign:read sign:write | Full signing access |
extended | sign:read sign:write | Treated as standard signing access |
impersonation | Not supported | Use Propper service credentials instead |
Docusign scope definitions are documented in their OAuth scopes reference ↗.
SDK Migration Options
Option A: Replace SDK with Direct HTTP Calls (Recommended)
This is the most portable approach and avoids SDK coupling:
- Docusign SDK (Before)
- Direct API (After)
const docusign = require('docusign-esign');
const envelopesApi = new docusign.EnvelopesApi(apiClient);
const envelope = await envelopesApi.createEnvelope(accountId, {
envelopeDefinition: {
emailSubject: 'Please sign',
documents: [{ documentBase64: '...', documentId: '1', name: 'Contract.pdf' }],
recipients: {
signers: [{ email: 'signer@example.com', name: 'John', recipientId: '1' }],
},
status: 'sent',
},
});
const createEnvelope = async (accountId, accessToken) => {
const response = await fetch(
`https://api.propper.ai/restapi/v2.1/accounts/${accountId}/envelopes`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
emailSubject: 'Please sign',
documents: [{ documentBase64: '...', documentId: '1', name: 'Contract.pdf' }],
recipients: {
signers: [{ email: 'signer@example.com', name: 'John', recipientId: '1' }],
},
status: 'sent',
}),
},
);
return response.json();
};
Option B: Thin Compatibility Wrapper
If you have extensive SDK usage, create a compatibility wrapper to minimize code changes:
// propper-docusign-compat.js
class PropperEnvelopesApi {
constructor(baseUrl, accessToken) {
this.baseUrl = baseUrl;
this.accessToken = accessToken;
}
async createEnvelope(accountId, { envelopeDefinition }) {
const response = await fetch(`${this.baseUrl}/restapi/v2.1/accounts/${accountId}/envelopes`, {
method: 'POST',
headers: {
Authorization: `Bearer ${this.accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(envelopeDefinition),
});
return response.json();
}
async getEnvelope(accountId, envelopeId) {
const response = await fetch(
`${this.baseUrl}/restapi/v2.1/accounts/${accountId}/envelopes/${envelopeId}`,
{
headers: { Authorization: `Bearer ${this.accessToken}` },
},
);
return response.json();
}
async createRecipientView(accountId, envelopeId, viewRequest) {
const response = await fetch(
`${this.baseUrl}/restapi/v2.1/accounts/${accountId}/envelopes/${envelopeId}/views/recipient`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${this.accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(viewRequest),
},
);
return response.json();
}
// Add more methods as needed...
}
// Usage - minimal code changes from Docusign SDK
const envelopesApi = new PropperEnvelopesApi('https://api.propper.ai', accessToken);
const envelope = await envelopesApi.createEnvelope(accountId, {
envelopeDefinition: {
/* same structure as before */
},
});
Embedded Signing Migration
When migrating recipient views (embedded signing ↗), ensure your request includes the required fields:
// Recipient view request - same structure for both Docusign and Propper
const viewRequest = {
returnUrl: 'https://yourapp.com/signing-complete', // Required
authenticationMethod: 'none',
clientUserId: 'user-123', // Required for embedded signing
email: 'signer@example.com',
userName: 'John Smith',
recipientId: '1',
};
const response = await fetch(
`${baseUrl}/restapi/v2.1/accounts/${accountId}/envelopes/${envelopeId}/views/recipient`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(viewRequest),
},
);
const { url } = await response.json();
// Redirect user to `url` for signing
Request/Response Compatibility
For core envelope operations, Propper follows Docusign v2.1 request shapes (envelopeDefinition, recipients, documents, tabs). The Docusign envelope resource reference ↗ is the baseline for payload structure.
Create Envelope Request
{
"emailSubject": "Please sign this document",
"emailBlurb": "Please review and sign at your convenience.",
"status": "sent",
"documents": [
{
"documentId": "1",
"name": "Contract.pdf",
"documentBase64": "JVBERi0xLjQKJeLjz9M...",
"order": "1"
}
],
"recipients": {
"signers": [
{
"email": "signer@example.com",
"name": "John Smith",
"recipientId": "1",
"routingOrder": "1",
"clientUserId": "user-123",
"tabs": {
"signHereTabs": [
{
"documentId": "1",
"pageNumber": "1",
"xPosition": "100",
"yPosition": "500"
}
]
}
}
]
}
}
This exact payload structure works with both Docusign and Propper.
Webhooks: Choose the Right Model
Docusign Connect and Propper Webhooks are separate systems with different purposes:
| System | Direction | Purpose |
|---|---|---|
| Docusign Connect | Docusign → Propper | Import envelopes, sync status during migration |
| Propper Webhooks | Propper → Your app | Notify your backend of Propper events |
If You Previously Used Docusign Connect
Docusign Connect is Docusign's webhook system with defined event triggers ↗ (e.g., envelope-completed, recipient-completed).
During migration, you can keep Docusign Connect running and configure Propper to receive those events. This is recommended for gradual transition and historical import. See Migrating Your Data from Docusign.
If You Want Propper → Your Backend Notifications
Use Propper Webhooks and subscribe to Propper event names.
Propper Webhook Event Names:
| Event | Description |
|---|---|
agreement.created | Agreement created |
agreement.sent | Agreement sent for signature |
agreement.delivered | Every recipient's invitation email was delivered |
agreement.viewed | Recipient opened the signing page for the first time |
agreement.recipient_signed | Individual recipient completed signing |
agreement.completed | All recipients completed, envelope finalized |
agreement.declined | Recipient declined to sign |
agreement.voided | Envelope was cancelled |
agreement.recalled | Sender recalled the envelope for editing before anyone signed |
agreement.expired | Envelope expired before it was completed |
In Docusign, recipient-delivered means a recipient opened the envelope, and envelope-delivered means all of them have. The Propper event for a recipient opening the envelope is agreement.viewed. Propper's agreement.delivered means the invitation emails reached the recipients' mail servers. Payload shapes are in Sign Webhooks.
Do not reuse your Docusign Connect HMAC key for Propper webhook verification. These are separate systems with separate secrets. Generate a new webhook secret in the Propper dashboard.
Running Both During Migration
You can run Docusign Connect (Docusign → Propper) and Propper Webhooks (Propper → your app) simultaneously:
Docusign → Docusign Connect → Propper (imports envelope)
↓
Propper Webhooks → Your Backend (notifies of import)
Feature Parity
Fully Supported (Core Workflows)
| Feature | Compatibility | Notes |
|---|---|---|
| Envelope create/send | ✅ Full | Same API structure |
| Multiple recipients | ✅ Full | Sequential and parallel routing |
| Embedded signing (recipient view) | ✅ Full | Same views/recipient ↗ API |
| Document upload (Base64) | ✅ Full | Same format |
| Envelope status tracking | ✅ Full | Docusign-compatible lifecycle ↗ |
| Document download | ✅ Full | Same documents API |
| Void envelopes | ✅ Full | Same API |
| Audit trail | ✅ Full | JSON and PDF certificate |
Tabs (Signature Fields)
Docusign supports many tab types ↗. Propper supports the most commonly used:
| Tab Type | Status | Notes |
|---|---|---|
signHereTabs | ✅ Supported | Primary signature placement |
initialHereTabs | ✅ Supported | Initial placement |
dateSignedTabs | ✅ Supported | Auto-filled signing date |
textTabs | ✅ Supported | Free-form text input |
fullNameTabs | ✅ Supported | Auto-filled signer name |
emailTabs | ✅ Supported | Auto-filled signer email |
companyTabs | ✅ Supported | Company name field |
titleTabs | ✅ Supported | Title/role field |
checkboxTabs | ⚠️ Partial | Basic support, complex validation coming |
radioGroupTabs | ⚠️ Partial | Basic support |
listTabs (dropdowns) | ⚠️ Partial | Basic support |
formulaTabs | ❌ Not yet | Calculated fields on roadmap |
notaryTabs | ❌ Not yet | Notarization not supported |
attachmentTabs | ❌ Not yet | On roadmap |
If your integration uses unsupported tab types, contact support to discuss alternatives or timeline.
Templates
| Feature | Status | Notes |
|---|---|---|
| Create templates | ✅ Supported | Same template API structure |
| Use templates for envelopes | ✅ Supported | Reference by template ID |
| Template roles | ✅ Supported | Map recipients to roles |
| Template tabs | ⚠️ Partial | Depends on tab type support |
Not Supported (Use Alternatives)
| Feature | Alternative |
|---|---|
| Docusign Admin API | Use Propper Admin dashboard |
| Bulk send | Loop with standard envelope API |
| PowerForms | Use embedded signing with your own forms |
| SMS delivery | Email delivery only |
| In-person signing | Standard embedded signing |
| IDV (ID Verification) | On roadmap |
Error Handling
Propper Error Response Format
All Propper API errors return a consistent structure:
{
"error": {
"code": "ENVELOPE_NOT_FOUND",
"message": "The requested envelope does not exist",
"details": {
"envelopeId": "abc123-def456"
},
"requestId": "req_7f8g9h0i1j2k"
}
}
| Field | Description |
|---|---|
code | Machine-readable error code for programmatic handling |
message | Human-readable error description |
details | Additional context (optional, varies by error) |
requestId | Unique identifier for support/debugging |
Error Code Mapping
| Docusign Error | Propper Error | HTTP Status |
|---|---|---|
ENVELOPE_NOT_IN_CORRECT_STATE | ENVELOPE_CANNOT_BE_MODIFIED | 400 |
ENVELOPE_DOES_NOT_EXIST | ENVELOPE_NOT_FOUND | 404 |
RECIPIENT_NOT_IN_SEQUENCE | RECIPIENT_NOT_FOUND | 404 |
INVALID_REQUEST_BODY | INVALID_REQUEST_BODY | 400 |
USER_AUTHENTICATION_FAILED | UNAUTHORIZED | 401 |
USER_NOT_AUTHORIZED_FOR_ACCOUNT | FORBIDDEN | 403 |
HOURLY_API_LIMIT_EXCEEDED | RATE_LIMIT_EXCEEDED | 429 |