Migrating from DocuSign
This guide helps you migrate existing DocuSign integrations to Propper's Sign API. The API is designed to be DocuSign-compatible, making migration straightforward.
Overview
Propper's Sign API implements the DocuSign eSignature REST API v2.1 specification. Most existing code will work with minimal changes:
- Update base URLs
- Update authentication
- Adjust for any feature differences
Quick Migration Checklist
- Update API base URL
- Update authentication to use Propper OAuth
- Replace DocuSign SDK with direct API calls or Propper SDK
- Update webhook endpoints (same format, different source)
- Test all signing workflows
- Update error handling for Propper-specific codes
AI-Assisted Migration
Use this prompt with Claude, ChatGPT, or your preferred AI assistant to analyze your codebase and generate a customized migration plan:
📋 Copy Migration Assistant Prompt
You are an expert at migrating DocuSign integrations to Propper Sign API. I need your help migrating my codebase.
## Your Task
1. **Analyze** - Search my codebase for all DocuSign integration points
2. **Ask Questions** - Clarify my requirements and current implementation
3. **Plan** - Create a detailed, step-by-step migration plan
## What to Look For
Search for these patterns in my code:
- DocuSign SDK imports (e.g., `docusign-esign`, `DocuSign.eSign`)
- Base URLs containing `docusign.net` or `docusign.com`
- Environment variables like `DOCUSIGN_*`
- JWT authentication with DocuSign
- Envelope creation, recipient management, embedded signing
- Webhook handlers for DocuSign Connect events
- Any DocuSign-specific error handling
## Questions to Ask Me
Before creating the plan, ask me about:
1. Which DocuSign features am I using? (envelopes, templates, embedded signing, webhooks, etc.)
2. What's my current authentication method? (JWT, Authorization Code, etc.)
3. Do I use the DocuSign SDK or direct API calls?
4. What languages/frameworks is my integration built with?
5. Do I have any custom webhook handlers?
6. What's my timeline and can I do a phased migration?
7. Are there any compliance or audit requirements I need to maintain?
## Migration Reference
Key changes from DocuSign to Propper:
| Component | DocuSign | Propper |
|-----------|----------|---------|
| Base URL | `https://na1.docusign.net` | `https://api.propper.ai` |
| Auth URL | `https://account.docusign.com` | `https://auth.propper.ai` |
| Auth Method | JWT or Auth Code | OAuth Client Credentials |
| SDK | `docusign-esign` | Direct API (fetch/axios) |
| Scopes | `signature` | `sign:read sign:write` |
API paths are identical - just change the base URL:
- `POST /restapi/v2.1/accounts/{accountId}/envelopes`
- `GET /restapi/v2.1/accounts/{accountId}/envelopes/{envelopeId}`
- `POST /restapi/v2.1/accounts/{accountId}/envelopes/{envelopeId}/views/recipient`
## Output Format
After analyzing and asking questions, provide:
1. **Current State Summary** - What DocuSign features I'm using
2. **Migration Complexity Assessment** - Low/Medium/High with reasoning
3. **Step-by-Step Migration Plan** - Detailed implementation steps (see below)
4. **Potential Risks** - What might break and how to mitigate
5. **Rollback Plan** - How to revert if needed
## Implementation Steps Format
For each migration step, provide:
### Step N: [Step Name]
**Files to modify:**
- path/to/file.js (lines X-Y)
**Before:** [show current DocuSign code]
**After:** [show new Propper code]
**Validation Checkpoint:**
- [ ] Run: [specific test command]
- [ ] Verify: [what to check]
- [ ] Expected result: [what success looks like]
## Validation Checkpoints
Include these checkpoints at key stages:
### Checkpoint 1: Authentication Migration
- [ ] Propper OAuth credentials configured in environment
- [ ] Token request returns valid access_token
- [ ] Token refresh works before expiry
- [ ] Test command: `curl -X POST https://auth.propper.ai/oauth/token -d '...'`
### Checkpoint 2: API Integration
- [ ] Create a draft envelope (status: "created")
- [ ] Retrieve the envelope by ID
- [ ] List envelopes returns results
- [ ] Test command: `curl https://api.propper.ai/restapi/v2.1/accounts/{id}/envelopes`
### Checkpoint 3: Signing Flow
- [ ] Generate recipient signing URL
- [ ] Complete a test signature
- [ ] Verify envelope status changes to "completed"
- [ ] Download signed document
### Checkpoint 4: Webhooks (if applicable)
- [ ] Webhook endpoint receives events
- [ ] Signature verification passes
- [ ] Events processed correctly (sent, viewed, completed)
### Checkpoint 5: Production Readiness
- [ ] All unit tests pass
- [ ] Integration tests pass
- [ ] Error handling covers Propper error codes
- [ ] Monitoring/logging updated
- [ ] Documentation updated
Start by searching my codebase for DocuSign integration points, then ask your clarifying questions.
For best results, use this prompt with an AI coding assistant that has access to your codebase (like Claude Code, Cursor, or GitHub Copilot). The AI can then search your files directly and provide specific file paths and line numbers.
API Endpoint Mapping
Base URL
| Environment | DocuSign | Propper |
|---|---|---|
| Production | https://na1.docusign.net | https://api.propper.ai |
Endpoint Paths
The path structure is identical. Simply replace the base URL:
| Operation | Path (same for both) |
|---|---|
| Create envelope | POST /restapi/v2.1/accounts/{accountId}/envelopes |
| Get envelope | GET /restapi/v2.1/accounts/{accountId}/envelopes/{envelopeId} |
| List envelopes | GET /restapi/v2.1/accounts/{accountId}/envelopes |
| Update envelope | PUT /restapi/v2.1/accounts/{accountId}/envelopes/{envelopeId} |
| Get recipients | GET /restapi/v2.1/accounts/{accountId}/envelopes/{envelopeId}/recipients |
| Recipient view | POST /restapi/v2.1/accounts/{accountId}/envelopes/{envelopeId}/views/recipient |
| Sender view | POST /restapi/v2.1/accounts/{accountId}/envelopes/{envelopeId}/views/sender |
| List documents | GET /restapi/v2.1/accounts/{accountId}/envelopes/{envelopeId}/documents |
| Download document | GET /restapi/v2.1/accounts/{accountId}/envelopes/{envelopeId}/documents/{documentId} |
Authentication Migration
DocuSign JWT Flow → Propper OAuth
- DocuSign (Before)
- Propper (After)
// DocuSign JWT authentication
const docusign = require('docusign-esign');
const apiClient = new docusign.ApiClient();
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/oauth/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',
}),
});
const { access_token } = await response.json();
return access_token;
};
Scopes Mapping
| DocuSign Scope | Propper Scope | Description |
|---|---|---|
signature | sign:read sign:write | Full signing access |
extended | sign:read sign:write | Same as signature |
impersonation | Not supported | Use service accounts instead |
SDK Migration
Option 1: Replace SDK with Direct API Calls
The most portable approach is to use direct HTTP calls:
- 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 2: Create a Thin Wrapper
If you have extensive SDK usage, create a compatibility wrapper:
// 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 listStatusChanges(accountId, options = {}) {
const params = new URLSearchParams();
if (options.fromDate) params.set('from_date', options.fromDate);
if (options.status) params.set('status', options.status);
const response = await fetch(
`${this.baseUrl}/restapi/v2.1/accounts/${accountId}/envelopes?${params}`,
{
headers: { 'Authorization': `Bearer ${this.accessToken}` },
}
);
return response.json();
}
// Add more methods as needed...
}
// Usage - minimal code changes
const envelopesApi = new PropperEnvelopesApi(
'https://api.propper.ai',
accessToken
);
const envelope = await envelopesApi.createEnvelope(accountId, {
envelopeDefinition: { /* same structure as before */ },
});
Request/Response Format
The request and response formats are identical. Here's a comparison:
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 works with both DocuSign and Propper.
Envelope Response
{
"envelopeId": "abc123-def456-...",
"status": "sent",
"statusDateTime": "2024-01-15T10:30:00Z",
"sentDateTime": "2024-01-15T10:30:00Z",
"emailSubject": "Please sign this document",
"emailBlurb": "Please review and sign at your convenience."
}
Feature Parity
Fully Supported Features
| Feature | Support Level | Notes |
|---|---|---|
| Create/send envelopes | Full | Same API |
| Multiple recipients | Full | Sequential and parallel routing |
| Embedded signing | Full | Same recipient view API |
| Document upload (Base64) | Full | Same format |
| Recipient types (signer, cc, viewer) | Full | Same structure |
| Envelope status tracking | Full | Same statuses |
| Webhooks/Connect | Full | Same event format |
| Void envelopes | Full | Same API |
| Audit trail | Full | JSON and PDF certificate |
| Templates | Full | Same template API |
Partially Supported Features
| Feature | Support Level | Notes |
|---|---|---|
| Tabs (signature fields) | Partial | Basic tabs supported, advanced tabs coming |
| Bulk send | Partial | Use loop with standard API |
| PowerForms | Not yet | Roadmap item |
| SMS delivery | Not yet | Email only currently |
| IDV (ID Verification) | Not yet | Roadmap item |
Not Supported
| Feature | Alternative |
|---|---|
| DocuSign Connect (push webhooks to specific URL) | Use Propper webhooks with same payload format |
| DocuSign Admin API | Use Propper Admin dashboard |
| Click (clickwrap) | Use Propper Click API (separate product) |
Webhook Migration
Webhook payloads follow the same structure. Update your endpoint URL in the Propper dashboard:
- DocuSign Webhook
- Propper Webhook
// DocuSign Connect webhook handler
app.post('/docusign/webhook', (req, res) => {
const event = req.body;
if (event.event === 'envelope-completed') {
const envelopeId = event.data.envelopeId;
// Process completed envelope
}
res.status(200).send();
});
// Propper webhook handler - same structure!
app.post('/propper/webhook', (req, res) => {
// Verify signature (recommended)
const signature = req.headers['x-propper-signature'];
if (!verifySignature(req.body, signature)) {
return res.status(401).send();
}
const event = req.body;
if (event.type === 'document.completed') {
const envelopeId = event.data.document.id;
// Process completed envelope
}
res.status(200).send();
});
Webhook Event Mapping
| DocuSign Event | Propper Event |
|---|---|
envelope-sent | document.sent |
envelope-delivered | document.viewed |
envelope-completed | document.completed |
envelope-declined | document.declined |
envelope-voided | document.voided |
recipient-sent | document.sent (per recipient) |
recipient-completed | document.signed |
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 |
Step-by-Step Migration
1. Set Up Propper Account
- Sign up at app.propper.ai
- Create an organization
- Generate API credentials (Settings → API Keys)
2. Update Environment Variables
# Before (DocuSign)
DOCUSIGN_BASE_URL=https://na1.docusign.net
DOCUSIGN_ACCOUNT_ID=abc123
DOCUSIGN_INTEGRATION_KEY=xyz789
DOCUSIGN_USER_ID=user123
DOCUSIGN_PRIVATE_KEY_PATH=./private.key
# After (Propper)
PROPPER_BASE_URL=https://api.propper.ai
PROPPER_ACCOUNT_ID=your-org-id
PROPPER_CLIENT_ID=your-client-id
PROPPER_CLIENT_SECRET=your-client-secret
3. Update Authentication Code
Replace DocuSign JWT with Propper OAuth:
// auth.js
const getAccessToken = async () => {
const response = await fetch(`${process.env.PROPPER_AUTH_URL}/oauth/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();
// Cache token until expiry
tokenCache = {
token: access_token,
expiresAt: Date.now() + (expires_in * 1000) - 60000, // 1 min buffer
};
return access_token;
};
4. Update API Calls
Find and replace the base URL:
// Before
const DOCUSIGN_BASE = 'https://na1.docusign.net/restapi/v2.1';
// After
const PROPPER_BASE = 'https://api.propper.ai/restapi/v2.1';
5. Update Webhooks
- Log in to Propper dashboard
- Go to Settings → Webhooks
- Add your webhook endpoint URL
- Select events to subscribe to
- Copy the webhook secret for signature verification
6. Test Thoroughly
# Test envelope creation
curl -X POST "$PROPPER_BASE/accounts/$ACCOUNT_ID/envelopes" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"emailSubject":"Test","status":"created","documents":[...]}'
# Test envelope retrieval
curl "$PROPPER_BASE/accounts/$ACCOUNT_ID/envelopes/$ENVELOPE_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN"
# Test embedded signing
curl -X POST "$PROPPER_BASE/accounts/$ACCOUNT_ID/envelopes/$ENVELOPE_ID/views/recipient" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"returnUrl":"https://yourapp.com/done","clientUserId":"user-123",...}'
Common Migration Issues
Issue: "Account not found" errors
Cause: Using DocuSign account ID instead of Propper organization ID.
Solution: Get your organization ID from the Propper dashboard (Settings → Organization).
Issue: Authentication failures
Cause: Using DocuSign JWT format.
Solution: Switch to OAuth client credentials flow (see authentication section above).
Issue: Webhook signature validation fails
Cause: Using DocuSign HMAC key.
Solution: Get new webhook secret from Propper dashboard and update verification code.
Issue: Missing tabs on documents
Cause: Some advanced tab types not yet supported.
Solution: Use basic tab types (signHere, dateSigned, text) or contact support for timeline on specific tabs.
Need Help?
- API Reference - Full endpoint documentation
- Error Handling Guide - Troubleshooting errors
- Support - Contact our migration team