Documenso

Common Workflows

Complete code examples for common document signing workflows, with error handling that can be adapted for your specific use case.

Workflow 1: Send a Document for Signature

The most common workflow: upload a PDF, add recipients with signature fields, and send for signing.

Build the recipient payload with signature and date fields

Create the envelope via POST /envelope/create with the PDF and payload

Distribute the document via POST /envelope/distribute to send signing links

const API_TOKEN = process.env.DOCUMENSO_API_TOKEN;
const BASE_URL = 'https://app.documenso.com/api/v2';

type Recipient = {
  email: string;
  name: string;
  role: 'SIGNER' | 'APPROVER' | 'CC' | 'VIEWER';
};

type CreateAndSendResult = {
  envelopeId: string;
  recipients: Array<{
    email: string;
    signingUrl: string;
  }>;
};

async function createAndSendDocument(
  pdfBuffer: Buffer,
  filename: string,
  title: string,
  recipients: Recipient[],
): Promise<CreateAndSendResult> {
  const recipientPayload = recipients.map((recipient, index) => ({
    email: recipient.email,
    name: recipient.name,
    role: recipient.role,
    signingOrder: index + 1,
    fields:
      recipient.role === 'SIGNER'
        ? [
            {
              identifier: 0,
              type: 'SIGNATURE',
              page: 1,
              positionX: 10,
              positionY: 80 - index * 10,
              width: 30,
              height: 5,
            },
            {
              identifier: 0,
              type: 'DATE',
              page: 1,
              positionX: 50,
              positionY: 80 - index * 10,
              width: 20,
              height: 3,
            },
          ]
        : [],
  }));

  const formData = new FormData();
  formData.append(
    'payload',
    JSON.stringify({
      type: 'DOCUMENT',
      title,
      recipients: recipientPayload,
      meta: {
        subject: `Please sign: ${title}`,
        message: 'Please review and sign the attached document.',
      },
    }),
  );
  formData.append('files', new Blob([pdfBuffer], { type: 'application/pdf' }), filename);

  const createResponse = await fetch(`${BASE_URL}/envelope/create`, {
    method: 'POST',
    headers: { Authorization: API_TOKEN },
    body: formData,
  });

  if (!createResponse.ok) {
    const error = await createResponse.json();
    throw new Error(`Failed to create envelope: ${error.message}`);
  }

  const { id: envelopeId } = await createResponse.json();

  const distributeResponse = await fetch(`${BASE_URL}/envelope/distribute`, {
    method: 'POST',
    headers: {
      Authorization: API_TOKEN,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ envelopeId }),
  });

  if (!distributeResponse.ok) {
    const error = await distributeResponse.json();
    throw new Error(`Failed to send document: ${error.message}`);
  }

  const distributeResult = await distributeResponse.json();

  return {
    envelopeId,
    recipients: distributeResult.recipients.map(
      (r: { email: string; signingUrl: string }) => ({
        email: r.email,
        signingUrl: r.signingUrl,
      }),
    ),
  };
}

const fs = require('fs');
const pdfBuffer = fs.readFileSync('./contract.pdf');

const result = await createAndSendDocument(
  pdfBuffer,
  'contract.pdf',
  'Service Agreement',
  [
    { email: 'client@example.com', name: 'John Smith', role: 'SIGNER' },
    { email: 'manager@company.com', name: 'Jane Doe', role: 'SIGNER' },
    { email: 'legal@company.com', name: 'Legal Team', role: 'CC' },
  ],
);

console.log('Document sent:', result.envelopeId);
result.recipients.forEach((r) => console.log(`${r.email}: ${r.signingUrl}`));
#!/bin/bash
set -e

API_TOKEN="YOUR_API_TOKEN"
BASE_URL="https://app.documenso.com/api/v2"
PDF_FILE="./contract.pdf"

# Step 1: Create envelope with recipients and fields
echo "Creating envelope..."
ENVELOPE_RESPONSE=$(curl -s -X POST "${BASE_URL}/envelope/create" \
  -H "Authorization: ${API_TOKEN}" \
  -F 'payload={
    "type": "DOCUMENT",
    "title": "Service Agreement",
    "recipients": [
      {
        "email": "client@example.com",
        "name": "John Smith",
        "role": "SIGNER",
        "signingOrder": 1,
        "fields": [
          {
            "identifier": 0,
            "type": "SIGNATURE",
            "page": 1,
            "positionX": 10,
            "positionY": 80,
            "width": 30,
            "height": 5
          },
          {
            "identifier": 0,
            "type": "DATE",
            "page": 1,
            "positionX": 50,
            "positionY": 80,
            "width": 20,
            "height": 3
          }
        ]
      },
      {
        "email": "manager@company.com",
        "name": "Jane Doe",
        "role": "SIGNER",
        "signingOrder": 2,
        "fields": [
          {
            "identifier": 0,
            "type": "SIGNATURE",
            "page": 1,
            "positionX": 10,
            "positionY": 70,
            "width": 30,
            "height": 5
          }
        ]
      }
    ],
    "meta": {
      "subject": "Please sign: Service Agreement",
      "message": "Please review and sign the attached document."
    }
  }' \
  -F "files=@${PDF_FILE};type=application/pdf")

ENVELOPE_ID=$(echo $ENVELOPE_RESPONSE | jq -r '.id')
echo "Created envelope: ${ENVELOPE_ID}"

# Step 2: Distribute (send) the document
echo "Sending document..."
DISTRIBUTE_RESPONSE=$(curl -s -X POST "${BASE_URL}/envelope/distribute" \
  -H "Authorization: ${API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d "{\"envelopeId\": \"${ENVELOPE_ID}\"}")

echo "Document sent!"
echo $DISTRIBUTE_RESPONSE | jq '.recipients[] | {email, signingUrl}'

Workflow 2: Create Document from Template with Custom Data

Use templates for repeatable document workflows. This example creates an employment contract with prefilled data.

Fetch the template to get recipient and field IDs

Map template fields by label and build a prefillFields array

Call POST /template/use with recipients, prefill data, and distributeDocument: true

const API_TOKEN = process.env.DOCUMENSO_API_TOKEN;
const BASE_URL = 'https://app.documenso.com/api/v2';

type EmployeeData = {
  email: string;
  name: string;
  position: string;
  salary: number;
  startDate: string; // ISO 8601 format: YYYY-MM-DD
  department: string;
};

type TemplateField = {
  id: number;
  type: string;
  fieldMeta?: { label?: string };
};

type TemplateRecipient = {
  id: number;
  role: string;
  signingOrder: number;
};

async function sendEmploymentContract(
  templateId: number,
  employee: EmployeeData,
): Promise<{ documentId: string; signingUrl: string }> {
  const templateResponse = await fetch(`${BASE_URL}/template/${templateId}`, {
    headers: { Authorization: API_TOKEN },
  });

  if (!templateResponse.ok) {
    throw new Error('Template not found');
  }

  const template = await templateResponse.json();

  const employeeRecipient = template.recipients.find(
    (r: TemplateRecipient) => r.role === 'SIGNER' && r.signingOrder === 1,
  );

  if (!employeeRecipient) {
    throw new Error('Template missing employee recipient');
  }

  const fieldsByLabel = new Map<string, TemplateField>();
  template.fields.forEach((field: TemplateField) => {
    if (field.fieldMeta?.label) {
      fieldsByLabel.set(field.fieldMeta.label.toLowerCase(), field);
    }
  });

  const prefillFields = [];

  const positionField = fieldsByLabel.get('position') || fieldsByLabel.get('job title');
  if (positionField) {
    prefillFields.push({ id: positionField.id, type: 'text', value: employee.position });
  }

  const salaryField = fieldsByLabel.get('salary') || fieldsByLabel.get('compensation');
  if (salaryField) {
    prefillFields.push({
      id: salaryField.id,
      type: 'number',
      value: employee.salary.toString(),
    });
  }

  const startDateField = fieldsByLabel.get('start date');
  if (startDateField) {
    prefillFields.push({
      id: startDateField.id,
      type: 'date',
      value: employee.startDate,
    });
  }

  const departmentField = fieldsByLabel.get('department');
  if (departmentField) {
    prefillFields.push({
      id: departmentField.id,
      type: 'dropdown',
      value: employee.department,
    });
  }

  const createResponse = await fetch(`${BASE_URL}/template/use`, {
    method: 'POST',
    headers: {
      Authorization: API_TOKEN,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      templateId,
      recipients: [
        { id: employeeRecipient.id, email: employee.email, name: employee.name },
      ],
      prefillFields,
      override: {
        title: `Employment Contract - ${employee.name}`,
        subject: `Your Employment Contract at ${employee.department}`,
        message: `Hi ${employee.name},\n\nPlease review and sign your employment contract for the ${employee.position} position.\n\nStart date: ${employee.startDate}`,
      },
      distributeDocument: true,
      externalId: `emp-${Date.now()}-${employee.email.split('@')[0]}`,
    }),
  });

  if (!createResponse.ok) {
    const error = await createResponse.json();
    throw new Error(`Failed to create document: ${error.message}`);
  }

  const document = await createResponse.json();

  return {
    documentId: document.id,
    signingUrl: document.recipients[0].signingUrl,
  };
}

const result = await sendEmploymentContract(123, {
  email: 'alice.johnson@example.com',
  name: 'Alice Johnson',
  position: 'Senior Engineer',
  salary: 120000,
  startDate: '2025-03-01',
  department: 'Engineering',
});

console.log('Contract sent:', result.documentId);
console.log('Signing URL:', result.signingUrl);
#!/bin/bash
set -e

API_TOKEN="YOUR_API_TOKEN"
BASE_URL="https://app.documenso.com/api/v2"
TEMPLATE_ID=123

# Variables
EMPLOYEE_EMAIL="alice.johnson@example.com"
EMPLOYEE_NAME="Alice Johnson"
POSITION="Senior Engineer"
SALARY="120000"
START_DATE="2025-03-01"
DEPARTMENT="Engineering"

# Fetch template to get recipient ID
TEMPLATE=$(curl -s -X GET "${BASE_URL}/template/${TEMPLATE_ID}" \
  -H "Authorization: ${API_TOKEN}")

# Extract first signer recipient ID
RECIPIENT_ID=$(echo $TEMPLATE | jq '.recipients[] | select(.role == "SIGNER" and .signingOrder == 1) | .id')

# Create document from template with prefilled data
RESPONSE=$(curl -s -X POST "${BASE_URL}/template/use" \
  -H "Authorization: ${API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d "{
    \"templateId\": ${TEMPLATE_ID},
    \"recipients\": [
      {
        \"id\": ${RECIPIENT_ID},
        \"email\": \"${EMPLOYEE_EMAIL}\",
        \"name\": \"${EMPLOYEE_NAME}\"
      }
    ],
    \"prefillFields\": [
      {\"id\": 101, \"type\": \"text\", \"value\": \"${POSITION}\"},
      {\"id\": 102, \"type\": \"number\", \"value\": \"${SALARY}\"},
      {\"id\": 103, \"type\": \"date\", \"value\": \"${START_DATE}\"},
      {\"id\": 104, \"type\": \"dropdown\", \"value\": \"${DEPARTMENT}\"}
    ],
    \"override\": {
      \"title\": \"Employment Contract - ${EMPLOYEE_NAME}\",
      \"subject\": \"Your Employment Contract\",
      \"message\": \"Please review and sign your employment contract.\"
    },
    \"distributeDocument\": true,
    \"externalId\": \"emp-$(date +%s)-alice\"
  }")

echo "Document created:"
echo $RESPONSE | jq '{id, signingUrl: .recipients[0].signingUrl}'

Workflow 3: Bulk Send Documents

Send the same document to multiple recipients in parallel. Useful for policy acknowledgments, NDAs, or announcements.

Fetch the template and get the signer recipient slot ID

For each recipient, call POST /template/use with distributeDocument: true

Process in batches with a short delay to respect rate limits (e.g. 100 requests/minute)

const API_TOKEN = process.env.DOCUMENSO_API_TOKEN;
const BASE_URL = 'https://app.documenso.com/api/v2';

type BulkRecipient = {
  email: string;
  name: string;
};

type BulkSendResult = {
  successful: Array<{ email: string; envelopeId: string; signingUrl: string }>;
  failed: Array<{ email: string; error: string }>;
};

async function bulkSendFromTemplate(
  templateId: number,
  recipients: BulkRecipient[],
  concurrency = 5,
): Promise<BulkSendResult> {
  const templateResponse = await fetch(`${BASE_URL}/template/${templateId}`, {
    headers: { Authorization: API_TOKEN },
  });

  if (!templateResponse.ok) {
    throw new Error('Template not found');
  }

  const template = await templateResponse.json();
  const signerSlot = template.recipients.find(
    (r: { role: string }) => r.role === 'SIGNER',
  );

  if (!signerSlot) {
    throw new Error('Template has no signer recipient');
  }

  const results: BulkSendResult = { successful: [], failed: [] };

  for (let i = 0; i < recipients.length; i += concurrency) {
    const batch = recipients.slice(i, i + concurrency);

    const batchResults = await Promise.allSettled(
      batch.map(async (recipient) => {
        const response = await fetch(`${BASE_URL}/template/use`, {
          method: 'POST',
          headers: {
            Authorization: API_TOKEN,
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            templateId,
            recipients: [
              { id: signerSlot.id, email: recipient.email, name: recipient.name },
            ],
            distributeDocument: true,
            externalId: `bulk-${Date.now()}-${recipient.email}`,
          }),
        });

        if (!response.ok) {
          const error = await response.json();
          throw new Error(error.message || 'Unknown error');
        }

        const document = await response.json();
        return {
          email: recipient.email,
          envelopeId: document.id,
          signingUrl: document.recipients[0].signingUrl,
        };
      }),
    );

    batchResults.forEach((result, index) => {
      if (result.status === 'fulfilled') {
        results.successful.push(result.value);
      } else {
        results.failed.push({
          email: batch[index].email,
          error: result.reason.message,
        });
      }
    });

    if (i + concurrency < recipients.length) {
      await new Promise((resolve) => setTimeout(resolve, 1000));
    }
  }

  return results;
}

const recipients = [
  { email: 'employee1@company.com', name: 'Employee One' },
  { email: 'employee2@company.com', name: 'Employee Two' },
  { email: 'employee3@company.com', name: 'Employee Three' },
];

const results = await bulkSendFromTemplate(456, recipients);

console.log(`Sent: ${results.successful.length}`);
console.log(`Failed: ${results.failed.length}`);
results.failed.forEach((f) => console.error(`${f.email}: ${f.error}`));
#!/bin/bash

API_TOKEN="YOUR_API_TOKEN"
BASE_URL="https://app.documenso.com/api/v2"
TEMPLATE_ID=456

# Get signer slot ID from template
SIGNER_ID=$(curl -s -X GET "${BASE_URL}/template/${TEMPLATE_ID}" \
  -H "Authorization: ${API_TOKEN}" | jq '.recipients[] | select(.role == "SIGNER") | .id')

# Recipients to process
RECIPIENTS=(
  "employee1@company.com:Employee One"
  "employee2@company.com:Employee Two"
  "employee3@company.com:Employee Three"
)

# Send to each recipient
for RECIPIENT in "${RECIPIENTS[@]}"; do
  EMAIL=$(echo $RECIPIENT | cut -d: -f1)
  NAME=$(echo $RECIPIENT | cut -d: -f2)

  echo "Sending to ${EMAIL}..."

  RESPONSE=$(curl -s -X POST "${BASE_URL}/template/use" \
    -H "Authorization: ${API_TOKEN}" \
    -H "Content-Type: application/json" \
    -d "{
      \"templateId\": ${TEMPLATE_ID},
      \"recipients\": [{
        \"id\": ${SIGNER_ID},
        \"email\": \"${EMAIL}\",
        \"name\": \"${NAME}\"
      }],
      \"distributeDocument\": true,
      \"externalId\": \"bulk-$(date +%s)-${EMAIL}\"
    }")

  if echo $RESPONSE | jq -e '.id' > /dev/null 2>&1; then
    echo "  Success: $(echo $RESPONSE | jq -r '.id')"
  else
    echo "  Failed: $(echo $RESPONSE | jq -r '.message')"
  fi

  # Rate limiting delay
  sleep 0.5
done

The API allows 100 requests per minute. For large batches, implement rate limiting with delays between requests to avoid hitting limits.


Workflow 4: Wait for Completion with Webhooks

Set up webhooks to receive real-time notifications when documents are signed or completed.

Create a webhook endpoint: Implement an HTTP handler that verifies the signature and processes events

Register the webhook: Add your endpoint URL and secret in Team Settings → Webhooks

Optional: poll as fallback: For critical workflows, poll envelope status if webhooks are unreliable

Create a Webhook Endpoint

// Express.js webhook handler
import express from 'express';

const app = express();
app.use(express.json());

const WEBHOOK_SECRET = process.env.DOCUMENSO_WEBHOOK_SECRET;

type WebhookPayload = {
  event: string;
  payload: {
    id: number;
    externalId: string | null;
    status: string;
    title: string;
    completedAt: string | null;
    Recipient: Array<{
      id: number;
      email: string;
      name: string;
      signingStatus: string;
      signedAt: string | null;
      rejectionReason: string | null;
    }>;
  };
  createdAt: string;
};

app.post('/webhooks/documenso', (req, res) => {
  // Verify webhook signature
  const secret = req.headers['x-documenso-secret'] as string;

  if (secret !== WEBHOOK_SECRET) {
    console.error('Invalid webhook secret');
    return res.status(401).send('Unauthorized');
  }

  const { event, payload } = req.body as WebhookPayload;

  switch (event) {
    case 'DOCUMENT_SENT':
      console.log(`Document ${payload.id} sent to recipients`);
      // Update your database, notify users, etc.
      break;

    case 'DOCUMENT_OPENED':
      const opener = payload.Recipient.find((r) => r.signingStatus === 'NOT_SIGNED');
      console.log(`${opener?.name} opened document ${payload.id}`);
      break;

    case 'DOCUMENT_SIGNED':
      const signer = payload.Recipient.find((r) => r.signingStatus === 'SIGNED' && r.signedAt);
      console.log(`${signer?.name} signed document ${payload.id}`);
      // Trigger next steps in your workflow
      break;

    case 'DOCUMENT_COMPLETED':
      console.log(`Document ${payload.id} completed at ${payload.completedAt}`);
      // All signatures collected - trigger fulfillment
      handleDocumentCompleted(payload);
      break;

    case 'DOCUMENT_REJECTED':
      const rejecter = payload.Recipient.find((r) => r.signingStatus === 'REJECTED');
      console.log(`${rejecter?.name} rejected: ${rejecter?.rejectionReason}`);
      // Handle rejection - notify admin, restart process, etc.
      break;
  }

  // Always respond 200 quickly
  res.status(200).send('OK');
});

async function handleDocumentCompleted(payload: WebhookPayload['payload']) {
  // Example: Update order status, send confirmation, download PDF
  console.log(`Processing completed document: ${payload.externalId || payload.id}`);
  // Your business logic here
}

app.listen(3000, () => console.log('Webhook server running on port 3000'));

Register the Webhook

Register your endpoint in the Documenso dashboard:

Go to Team SettingsWebhooks

Click Create Webhook

Enter your webhook URL (e.g. https://your-app.com/webhooks/documenso)

Select which events to subscribe to: DOCUMENT_SENT, DOCUMENT_OPENED, DOCUMENT_SIGNED, DOCUMENT_COMPLETED, DOCUMENT_REJECTED

Enter your webhook secret (the same value stored in DOCUMENSO_WEBHOOK_SECRET)

Click Create Webhook

See Webhook Setup for detailed instructions.

Poll as Fallback

For critical workflows, implement polling as a fallback in case webhooks fail:

async function pollForCompletion(
  envelopeId: string,
  timeoutMs = 86400000, // 24 hours
  intervalMs = 60000, // 1 minute
): Promise<boolean> {
  const startTime = Date.now();

  while (Date.now() - startTime < timeoutMs) {
    const response = await fetch(`${BASE_URL}/envelope/${envelopeId}`, {
      headers: { Authorization: API_TOKEN },
    });

    const envelope = await response.json();

    if (envelope.status === 'COMPLETED') {
      return true;
    }

    if (envelope.status === 'REJECTED') {
      throw new Error('Document was rejected');
    }

    await new Promise((resolve) => setTimeout(resolve, intervalMs));
  }

  return false;
}

Workflow 5: Download Signed Documents

After a document is completed, download the signed PDF with all signatures embedded.

Get the envelope with GET /envelope/:envelopeId and confirm status is COMPLETED

Use envelopeItems[0].id and call GET /envelope/item/:itemId/download?version=signed

const API_TOKEN = process.env.DOCUMENSO_API_TOKEN;
const BASE_URL = 'https://app.documenso.com/api/v2';

type DownloadVersion = 'signed' | 'original';

async function downloadDocument(
  envelopeId: string,
  version: DownloadVersion = 'signed',
): Promise<{ buffer: Buffer; filename: string }> {
  const envelopeResponse = await fetch(`${BASE_URL}/envelope/${envelopeId}`, {
    headers: { Authorization: API_TOKEN },
  });

  if (!envelopeResponse.ok) {
    throw new Error('Envelope not found');
  }

  const envelope = await envelopeResponse.json();

  if (version === 'signed' && envelope.status !== 'COMPLETED') {
    throw new Error('Document must be completed to download signed version');
  }

  const envelopeItem = envelope.envelopeItems[0];
  if (!envelopeItem) {
    throw new Error('No document found in envelope');
  }

  const downloadResponse = await fetch(
    `${BASE_URL}/envelope/item/${envelopeItem.id}/download?version=${version}`,
    { headers: { Authorization: API_TOKEN } },
  );

  if (!downloadResponse.ok) {
    const error = await downloadResponse.json();
    throw new Error(`Download failed: ${error.message}`);
  }

  const buffer = Buffer.from(await downloadResponse.arrayBuffer());
  const filename = `${envelope.title.replace(/[^a-z0-9]/gi, '_')}_${version}.pdf`;

  return { buffer, filename };
}

async function downloadAllCompletedDocuments(outputDir: string): Promise<void> {
  const fs = require('fs');
  const path = require('path');

  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const response = await fetch(
      `${BASE_URL}/envelope?status=COMPLETED&type=DOCUMENT&page=${page}&perPage=50`,
      { headers: { Authorization: API_TOKEN } },
    );

    const { data, pagination } = await response.json();

    for (const envelope of data) {
      try {
        const { buffer, filename } = await downloadDocument(envelope.id, 'signed');
        const filepath = path.join(outputDir, filename);
        fs.writeFileSync(filepath, buffer);
        console.log(`Downloaded: ${filename}`);
      } catch (error) {
        console.error(`Failed to download ${envelope.id}:`, error);
      }
      await new Promise((resolve) => setTimeout(resolve, 500));
    }

    hasMore = page < pagination.totalPages;
    page++;
  }
}

const { buffer, filename } = await downloadDocument('envelope_abc123', 'signed');
require('fs').writeFileSync(`./downloads/${filename}`, buffer);

await downloadAllCompletedDocuments('./downloads');
#!/bin/bash

API_TOKEN="YOUR_API_TOKEN"
BASE_URL="https://app.documenso.com/api/v2"
ENVELOPE_ID="envelope_abc123"
OUTPUT_DIR="./downloads"

mkdir -p $OUTPUT_DIR

# Get envelope details
ENVELOPE=$(curl -s -X GET "${BASE_URL}/envelope/${ENVELOPE_ID}" \
  -H "Authorization: ${API_TOKEN}")

STATUS=$(echo $ENVELOPE | jq -r '.status')
TITLE=$(echo $ENVELOPE | jq -r '.title')
ITEM_ID=$(echo $ENVELOPE | jq -r '.envelopeItems[0].id')

# Check if completed
if [ "$STATUS" != "COMPLETED" ]; then
  echo "Error: Document status is ${STATUS}, must be COMPLETED to download signed version"
  exit 1
fi

# Download signed document
FILENAME="${TITLE// /_}_signed.pdf"
curl -s -X GET "${BASE_URL}/envelope/item/${ITEM_ID}/download?version=signed" \
  -H "Authorization: ${API_TOKEN}" \
  -o "${OUTPUT_DIR}/${FILENAME}"

echo "Downloaded: ${OUTPUT_DIR}/${FILENAME}"

# Download original (optional)
ORIG_FILENAME="${TITLE// /_}_original.pdf"
curl -s -X GET "${BASE_URL}/envelope/item/${ITEM_ID}/download?version=original" \
  -H "Authorization: ${API_TOKEN}" \
  -o "${OUTPUT_DIR}/${ORIG_FILENAME}"

echo "Downloaded: ${OUTPUT_DIR}/${ORIG_FILENAME}"

The signed PDF is only available after all recipients have completed signing (document status is COMPLETED). Attempting to download before completion returns the original document without signatures.


Error Handling Patterns

Implement robust error handling for production integrations.


See Also

On this page