feat: docs v2 (#2460)

Co-authored-by: Catalin Pit <catalinpit@gmail.com>
This commit is contained in:
Lucas Smith
2026-02-27 22:05:27 +11:00
committed by GitHub
parent f8ac782f2e
commit b92c53dbb2
290 changed files with 32521 additions and 266 deletions
@@ -0,0 +1,37 @@
---
title: Developer Mode
description: Advanced development tools for debugging field coordinates and integrating with the Documenso API.
---
## Overview
Developer mode provides additional tools and features to help you integrate and debug Documenso.
## Field Coordinates
Field coordinates represent the position of a field in a document. They are returned in the `pageX`, `pageY`, `width` and `height` properties of the field.
To enable field coordinates, add the `devmode=true` query parameter to the editor URL.
```bash
# Legacy editor
https://app.documenso.com/t/<team-url>/documents/<envelope-id>/legacy_editor?devmode=true
```
![Field Coordinates Legacy Editor](/developer-mode/field-coordinates-legacy-editor.webp)
```bash
# New editor
https://app.documenso.com/t/<team-url>/documents/<envelope-id>/edit?step=addFields&devmode=true
```
![Field Coordinates New Editor](/developer-mode/field-coordinates-new-editor.webp)
---
## See Also
- [Fields API](/docs/developers/api/fields) - Create and position fields via API
- [Field Types](/docs/concepts/field-types) - Detailed field type reference
@@ -0,0 +1,815 @@
---
title: Documents API
description: Create, manage, and send documents for signing via the API.
---
import { Callout } from 'fumadocs-ui/components/callout';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
<Callout type="warn">
This guide may not reflect the latest endpoints or parameters. For an always up-to-date reference,
see the [OpenAPI Reference](https://openapi.documenso.com).
</Callout>
## Overview
[Documents](/docs/users/documents) (called "envelopes" in the API) are the core resource in Documenso. You can:
1. create documents with recipients and fields
2. send them for signing
3. track their status
4. retrieve the completed PDFs
Each document contains one or more PDF files, a list of recipients, and the fields they need to fill.
## Document Object
A document object contains the following properties:
| Property | Type | Description |
| --------------- | -------------- | -------------------------------------------------------------- |
| `id` | string | Unique identifier (e.g., `envelope_abc123`) |
| `type` | string | `DOCUMENT` or `TEMPLATE` |
| `status` | string | Current status: `DRAFT`, `PENDING`, `COMPLETED`, or `REJECTED` |
| `title` | string | Document title |
| `source` | string | How the document was created: `DOCUMENT`, `TEMPLATE`, `API` |
| `visibility` | string | Who can view: `EVERYONE`, `ADMIN`, `MANAGER_AND_ABOVE` |
| `externalId` | string \| null | Your custom identifier for the document |
| `createdAt` | string | ISO 8601 timestamp |
| `updatedAt` | string | ISO 8601 timestamp |
| `completedAt` | string \| null | Timestamp when all recipients completed signing |
| `deletedAt` | string \| null | Timestamp if soft-deleted |
| `recipients` | array | List of recipients and their signing status |
| `fields` | array | Signature and form fields on the document |
| `envelopeItems` | array | PDF files attached to the document |
| `documentMeta` | object | Email settings, redirect URL, signing options |
### Example Document Object
```json
{
"id": "envelope_abc123xyz",
"type": "DOCUMENT",
"status": "PENDING",
"source": "API",
"visibility": "EVERYONE",
"title": "Service Agreement",
"externalId": "contract-2025-001",
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-15T10:35:00.000Z",
"completedAt": null,
"deletedAt": null,
"recipients": [
{
"id": 1,
"email": "signer@example.com",
"name": "John Smith",
"role": "SIGNER",
"signingStatus": "NOT_SIGNED",
"signingOrder": 1
}
],
"fields": [
{
"id": "field_123",
"type": "SIGNATURE",
"page": 1,
"positionX": 10,
"positionY": 80,
"width": 30,
"height": 5,
"recipientId": 1
}
],
"envelopeItems": [
{
"id": "envelope_item_xyz",
"title": "contract.pdf",
"order": 1
}
],
"documentMeta": {
"subject": "Please sign this document",
"message": "Hi, please review and sign this agreement.",
"timezone": "America/New_York",
"redirectUrl": "https://example.com/thank-you"
}
}
```
## List Documents
Retrieve a paginated list of documents.
```
GET /envelope
```
### Query Parameters
| Parameter | Type | Description |
| ------------------ | ------- | ------------------------------------------------------------- |
| `page` | integer | Page number (default: 1) |
| `perPage` | integer | Results per page (default: 10, max: 100) |
| `type` | string | Filter by `DOCUMENT` or `TEMPLATE` |
| `status` | string | Filter by status: `DRAFT`, `PENDING`, `COMPLETED`, `REJECTED` |
| `source` | string | Filter by creation source |
| `folderId` | string | Filter by folder ID |
| `orderByColumn` | string | Sort field (only `createdAt` supported) |
| `orderByDirection` | string | Sort direction: `asc` or `desc` (default: `desc`) |
### Code Examples
<Tabs items={['curl', 'TypeScript']}>
<Tab value="curl">
```bash
# List all documents
curl -X GET "https://app.documenso.com/api/v2/envelope" \
-H "Authorization: api_xxxxxxxxxxxxxxxx"
# Filter by status and paginate
curl -X GET "https://app.documenso.com/api/v2/envelope?status=PENDING&page=1&perPage=20" \
-H "Authorization: api_xxxxxxxxxxxxxxxx"
# List only documents (not templates)
curl -X GET "https://app.documenso.com/api/v2/envelope?type=DOCUMENT" \
-H "Authorization: api_xxxxxxxxxxxxxxxx"
````
</Tab>
<Tab value="TypeScript">
```typescript
const API_TOKEN = process.env.DOCUMENSO_API_TOKEN;
const BASE_URL = 'https://app.documenso.com/api/v2';
// List all documents
const response = await fetch(`${BASE_URL}/envelope`, {
method: 'GET',
headers: {
Authorization: API_TOKEN,
},
});
const { data, pagination } = await response.json();
console.log(`Found ${pagination.totalItems} documents`);
// Filter by status
const pendingResponse = await fetch(
`${BASE_URL}/envelope?status=PENDING&page=1&perPage=20`,
{
method: 'GET',
headers: {
Authorization: API_TOKEN,
},
}
);
const pendingDocs = await pendingResponse.json();
````
</Tab>
</Tabs>
### Response
```json
{
"data": [
{
"id": "envelope_abc123",
"type": "DOCUMENT",
"status": "PENDING",
"title": "Service Agreement",
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-15T10:35:00.000Z",
"recipients": [
{
"id": 1,
"email": "signer@example.com",
"name": "John Smith",
"role": "SIGNER",
"signingStatus": "NOT_SIGNED"
}
]
}
],
"pagination": {
"page": 1,
"perPage": 10,
"totalPages": 5,
"totalItems": 42
}
}
```
---
## Get Document
Retrieve a single document by ID.
```
GET /envelope/{envelopeId}
```
### Path Parameters
| Parameter | Type | Description |
| ------------ | ------ | ----------------------------------------- |
| `envelopeId` | string | The document ID (e.g., `envelope_abc123`) |
### Code Examples
<Tabs items={['curl', 'TypeScript']}>
<Tab value="curl">
```bash
curl -X GET "https://app.documenso.com/api/v2/envelope/envelope_abc123" \
-H "Authorization: api_xxxxxxxxxxxxxxxx"
```
</Tab>
<Tab value="TypeScript">
```typescript
const envelopeId = 'envelope_abc123';
const response = await fetch(`https://app.documenso.com/api/v2/envelope/${envelopeId}`, {
method: 'GET',
headers: {
Authorization: 'api_xxxxxxxxxxxxxxxx',
},
});
const document = await response.json();
console.log(document.title, document.status);
````
</Tab>
</Tabs>
### Response
Returns the full document object including recipients, fields, and envelope items.
```json
{
"id": "envelope_abc123",
"type": "DOCUMENT",
"status": "PENDING",
"title": "Service Agreement",
"recipients": [...],
"fields": [...],
"envelopeItems": [...],
"documentMeta": {...}
}
````
---
## Create Document
Create a new document with optional recipients and fields in a single request.
```
POST /envelope/create
Content-Type: multipart/form-data
```
### Request Body
The request uses `multipart/form-data` with two parts:
| Part | Type | Description |
| --------- | ------- | ---------------------- |
| `payload` | JSON | Document configuration |
| `files` | File(s) | One or more PDF files |
### Payload Schema
| Field | Type | Required | Description |
| ------------ | ------ | -------- | ------------------------------------------- |
| `type` | string | Yes | Must be `DOCUMENT` |
| `title` | string | Yes | Document title |
| `externalId` | string | No | Your custom identifier |
| `visibility` | string | No | `EVERYONE`, `ADMIN`, or `MANAGER_AND_ABOVE` |
| `folderId` | string | No | Folder ID to create the document in |
| `recipients` | array | No | Recipients with optional fields |
| `meta` | object | No | Email subject, message, redirect URL, etc. |
### Code Examples
<Tabs items={['curl', 'TypeScript']}>
<Tab value="curl">
```bash
curl -X POST "https://app.documenso.com/api/v2/envelope/create" \
-H "Authorization: api_xxxxxxxxxxxxxxxx" \
-H "Content-Type: multipart/form-data" \
-F 'payload={
"type": "DOCUMENT",
"title": "Service Agreement",
"externalId": "contract-2025-001",
"recipients": [
{
"email": "signer@example.com",
"name": "John Smith",
"role": "SIGNER",
"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
}
]
}
],
"meta": {
"subject": "Please sign this agreement",
"message": "Hi John, please review and sign the attached agreement.",
"redirectUrl": "https://example.com/thank-you"
}
}' \
-F "files=@./contract.pdf;type=application/pdf"
```
</Tab>
<Tab value="TypeScript">
```typescript
import fs from 'fs';
import FormData from 'form-data';
const form = new FormData();
const payload = {
type: 'DOCUMENT',
title: 'Service Agreement',
externalId: 'contract-2025-001',
recipients: [
{
email: 'signer@example.com',
name: 'John Smith',
role: 'SIGNER',
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,
},
],
},
],
meta: {
subject: 'Please sign this agreement',
message: 'Hi John, please review and sign the attached agreement.',
redirectUrl: 'https://example.com/thank-you',
},
};
form.append('payload', JSON.stringify(payload));
form.append('files', fs.createReadStream('./contract.pdf'), {
contentType: 'application/pdf',
});
const response = await fetch('https://app.documenso.com/api/v2/envelope/create', {
method: 'POST',
headers: {
Authorization: 'api_xxxxxxxxxxxxxxxx',
},
body: form,
});
const { id } = await response.json();
console.log('Created document:', id);
````
</Tab>
</Tabs>
### Response
```json
{
"id": "envelope_abc123xyz"
}
````
### Field Positioning
Field positions use percentage values (0-100) relative to the PDF page:
| Parameter | Description |
| ------------ | ---------------------------------------------------------- |
| `positionX` | Horizontal position from left edge (0 = left, 100 = right) |
| `positionY` | Vertical position from top edge (0 = top, 100 = bottom) |
| `width` | Field width as percentage of page width |
| `height` | Field height as percentage of page height |
| `page` | Page number (1-indexed) |
| `identifier` | File index (0 for first file) or filename |
### Field Types
| Type | Description |
| ----------- | --------------------------- |
| `SIGNATURE` | Signature field |
| `INITIALS` | Initials field |
| `NAME` | Auto-filled recipient name |
| `EMAIL` | Auto-filled recipient email |
| `DATE` | Signing date |
| `TEXT` | Free text input |
| `NUMBER` | Numeric input |
| `CHECKBOX` | Checkbox selection |
| `RADIO` | Radio button group |
| `DROPDOWN` | Dropdown selection |
### Recipient Roles
| Role | Description |
| ---------- | ----------------------------------------- |
| `SIGNER` | Must sign the document |
| `APPROVER` | Must approve before signers can sign |
| `CC` | Receives a copy but doesn't sign |
| `VIEWER` | Can view the document but takes no action |
---
## Update Document
Update a document's properties. Only works on documents in `DRAFT` status.
```
POST /envelope/update
```
### Request Body
| Field | Type | Required | Description |
| ------------ | ------ | -------- | ------------------------------------ |
| `envelopeId` | string | Yes | Document ID |
| `data` | object | No | Document properties to update |
| `meta` | object | No | Email and signing settings to update |
### Code Examples
<Tabs items={['curl', 'TypeScript']}>
<Tab value="curl">
```bash
curl -X POST "https://app.documenso.com/api/v2/envelope/update" \
-H "Authorization: api_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"envelopeId": "envelope_abc123",
"data": {
"title": "Updated Service Agreement",
"externalId": "contract-2025-001-v2"
},
"meta": {
"subject": "Updated: Please sign this agreement",
"redirectUrl": "https://example.com/signed"
}
}'
```
</Tab>
<Tab value="TypeScript">
```typescript
const response = await fetch('https://app.documenso.com/api/v2/envelope/update', {
method: 'POST',
headers: {
Authorization: 'api_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
envelopeId: 'envelope_abc123',
data: {
title: 'Updated Service Agreement',
externalId: 'contract-2025-001-v2',
},
meta: {
subject: 'Updated: Please sign this agreement',
redirectUrl: 'https://example.com/signed',
},
}),
});
const document = await response.json();
```
</Tab>
</Tabs>
---
## Send Document
Send a document to recipients for signing. This changes the status from `DRAFT` to `PENDING`.
```
POST /envelope/distribute
````
### Request Body
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `envelopeId` | string | Yes | Document ID |
| `meta` | object | No | Override email settings for this send |
### Code Examples
<Tabs items={['curl', 'TypeScript']}>
<Tab value="curl">
```bash
# Basic send
curl -X POST "https://app.documenso.com/api/v2/envelope/distribute" \
-H "Authorization: api_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"envelopeId": "envelope_abc123"
}'
# Send with custom email settings
curl -X POST "https://app.documenso.com/api/v2/envelope/distribute" \
-H "Authorization: api_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"envelopeId": "envelope_abc123",
"meta": {
"subject": "Action Required: Sign Agreement",
"message": "Please sign this document by end of day.",
"timezone": "America/New_York"
}
}'
````
</Tab>
<Tab value="TypeScript">
```typescript
const response = await fetch('https://app.documenso.com/api/v2/envelope/distribute', {
method: 'POST',
headers: {
Authorization: 'api_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
envelopeId: 'envelope_abc123',
meta: {
subject: 'Action Required: Sign Agreement',
message: 'Please sign this document by end of day.',
},
}),
});
const { id, recipients } = await response.json();
// Recipients now include signing URLs
recipients.forEach((r) => {
console.log(`${r.email}: ${r.signingUrl}`);
});
````
</Tab>
</Tabs>
### Response
The response includes signing URLs for each recipient:
```json
{
"success": true,
"id": "envelope_abc123",
"recipients": [
{
"id": 1,
"name": "John Smith",
"email": "signer@example.com",
"token": "abc123xyz",
"role": "SIGNER",
"signingOrder": 1,
"signingUrl": "https://app.documenso.com/sign/abc123xyz"
}
]
}
````
<Callout type="info">
Use the `signingUrl` to redirect recipients directly to the signing page, or let them use the
email link.
</Callout>
---
## Delete Document
Delete a document. Completed documents cannot be deleted.
```
POST /envelope/delete
```
### Request Body
| Field | Type | Required | Description |
| ------------ | ------ | -------- | ----------- |
| `envelopeId` | string | Yes | Document ID |
### Code Examples
<Tabs items={['curl', 'TypeScript']}>
<Tab value="curl">
```bash
curl -X POST "https://app.documenso.com/api/v2/envelope/delete" \
-H "Authorization: api_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"envelopeId": "envelope_abc123"
}'
```
</Tab>
<Tab value="TypeScript">
```typescript
const response = await fetch('https://app.documenso.com/api/v2/envelope/delete', {
method: 'POST',
headers: {
Authorization: 'api_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
envelopeId: 'envelope_abc123',
}),
});
const { success } = await response.json();
````
</Tab>
</Tabs>
### Response
```json
{
"success": true
}
````
---
## Get Multiple Documents
Retrieve multiple documents by their IDs in a single request.
```
POST /envelope/get-many
```
### Request Body
| Field | Type | Required | Description |
| ------------- | ----- | -------- | --------------------- |
| `envelopeIds` | array | Yes | Array of document IDs |
### Code Examples
<Tabs items={['curl', 'TypeScript']}>
<Tab value="curl">
```bash
curl -X POST "https://app.documenso.com/api/v2/envelope/get-many" \
-H "Authorization: api_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"envelopeIds": ["envelope_abc123", "envelope_def456", "envelope_ghi789"]
}'
```
</Tab>
<Tab value="TypeScript">
```typescript
const response = await fetch('https://app.documenso.com/api/v2/envelope/get-many', {
method: 'POST',
headers: {
Authorization: 'api_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
envelopeIds: ['envelope_abc123', 'envelope_def456', 'envelope_ghi789'],
}),
});
const documents = await response.json();
````
</Tab>
</Tabs>
---
## Document Statuses
| Status | Description |
| --- | --- |
| `DRAFT` | Document is being prepared. Recipients have not been notified. |
| `PENDING` | Document has been sent. Waiting for recipients to sign. |
| `COMPLETED` | All recipients have signed. Document is sealed. |
| `REJECTED` | A recipient rejected the document. |
### Status Transitions
```mermaid
flowchart LR
DRAFT --> PENDING --> COMPLETED
PENDING --> REJECTED
```
- **DRAFT to PENDING**: Call the distribute endpoint
- **PENDING to COMPLETED**: All recipients complete their signing
- **PENDING to REJECTED**: A recipient rejects the document
<Callout type="warn">
You cannot modify recipients or fields after a document moves to `PENDING` status.
</Callout>
---
## Filtering and Pagination
### Pagination Parameters
| Parameter | Type | Default | Description |
| --------- | ------- | ------- | --------------------------- |
| `page` | integer | 1 | Page number |
| `perPage` | integer | 10 | Results per page (max: 100) |
### Filter Parameters
| Parameter | Values | Description |
| ---------- | ------------------------------------------- | ------------------------- |
| `type` | `DOCUMENT`, `TEMPLATE` | Filter by envelope type |
| `status` | `DRAFT`, `PENDING`, `COMPLETED`, `REJECTED` | Filter by status |
| `source` | `DOCUMENT`, `TEMPLATE`, `API` | Filter by creation source |
| `folderId` | string | Filter by folder |
### Sorting
| Parameter | Values | Description |
| ------------------ | ------------- | -------------------------------- |
| `orderByColumn` | `createdAt` | Field to sort by |
| `orderByDirection` | `asc`, `desc` | Sort direction (default: `desc`) |
### Example: Fetch All Pending Documents
```typescript
async function getAllPendingDocuments() {
const documents = [];
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(
`https://app.documenso.com/api/v2/envelope?status=PENDING&page=${page}&perPage=100`,
{
headers: { Authorization: 'api_xxxxxxxxxxxxxxxx' },
},
);
const { data, pagination } = await response.json();
documents.push(...data);
hasMore = page < pagination.totalPages;
page++;
}
return documents;
}
```
---
## See Also
- [Recipients API](/docs/developers/api/recipients) - Add and manage document recipients
- [Fields API](/docs/developers/api/fields) - Add signature and form fields
- [Templates API](/docs/developers/api/templates) - Create reusable document templates
- [Webhooks](/docs/developers/webhooks) - Get notified when documents are signed
@@ -0,0 +1,738 @@
---
title: Fields API
description: Add signature and form fields to documents via API.
---
import { Callout } from 'fumadocs-ui/components/callout';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
<Callout type="warn">
This guide may not reflect the latest endpoints or parameters. For an always up-to-date reference,
see the [OpenAPI Reference](https://openapi.documenso.com).
</Callout>
## Field Object
| Property | Type | Description |
| ---------------- | -------------- | -------------------------------------------- |
| `id` | number | Unique field identifier |
| `secondaryId` | string | Secondary identifier for audit logs |
| `type` | string | Field type (see [Field Types](#field-types)) |
| `recipientId` | number | ID of the recipient assigned to this field |
| `envelopeId` | number | ID of the parent envelope |
| `envelopeItemId` | string | ID of the PDF item the field is placed on |
| `page` | number | Page number (1-indexed) |
| `positionX` | number | X coordinate as percentage (0-100) |
| `positionY` | number | Y coordinate as percentage (0-100) |
| `width` | number | Width as percentage of page (0-100) |
| `height` | number | Height as percentage of page (0-100) |
| `customText` | string | Value entered by the recipient |
| `inserted` | boolean | Whether the field has been completed |
| `fieldMeta` | object \| null | Type-specific configuration options |
### Example Field Object
```json
{
"id": 456,
"secondaryId": "field_abc123",
"type": "SIGNATURE",
"recipientId": 123,
"envelopeId": 789,
"envelopeItemId": "envelope_item_xyz",
"page": 1,
"positionX": 10,
"positionY": 80,
"width": 30,
"height": 5,
"customText": "",
"inserted": false,
"fieldMeta": {
"type": "signature",
"required": true
}
}
```
---
## Field Types
| Type | Description | Auto-filled |
| ---------------- | ----------------------------------------- | ----------- |
| `SIGNATURE` | Drawn, typed, or uploaded signature | No |
| `FREE_SIGNATURE` | Unrestricted signature without validation | No |
| `INITIALS` | Recipient's initials | No |
| `NAME` | Recipient's full name | Yes |
| `EMAIL` | Recipient's email address | Yes |
| `DATE` | Date the field was completed | Yes |
| `TEXT` | Free-form text input | No |
| `NUMBER` | Numeric input with optional validation | No |
| `RADIO` | Single selection from options | No |
| `CHECKBOX` | Multiple selections from options | No |
| `DROPDOWN` | Single selection from a dropdown menu | No |
---
## Get Field
Retrieve a single field by ID.
```
GET /envelope/field/{fieldId}
```
### Path Parameters
| Parameter | Type | Description |
| --------- | ------ | ------------ |
| `fieldId` | number | The field ID |
### Code Examples
<Tabs items={['curl', 'TypeScript']}>
<Tab value="curl">
```bash
curl -X GET "https://app.documenso.com/api/v2/envelope/field/456" \
-H "Authorization: api_xxxxxxxxxxxxxxxx"
```
</Tab>
<Tab value="TypeScript">
```typescript
const response = await fetch(
'https://app.documenso.com/api/v2/envelope/field/456',
{
method: 'GET',
headers: {
Authorization: 'api_xxxxxxxxxxxxxxxx',
},
}
);
const field = await response.json();
console.log(field.type, field.page);
```
</Tab>
</Tabs>
### Response
Returns the field object.
---
## Create Fields
Add one or more fields to a document.
```
POST /envelope/field/create-many
````
### Request Body
| Field | Type | Required | Description |
| ----------- | ------ | -------- | ------------------------------- |
| `documentId`| number | Yes | The document ID |
| `fields` | array | Yes | Array of field configurations |
### Code Examples
<Tabs items={['curl', 'TypeScript']}>
<Tab value="curl">
```bash
curl -X POST "https://app.documenso.com/api/v2/envelope/field/create-many" \
-H "Authorization: api_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"documentId": 123,
"fields": [
{
"type": "SIGNATURE",
"recipientId": 456,
"pageNumber": 1,
"pageX": 10,
"pageY": 80,
"width": 30,
"height": 5
},
{
"type": "DATE",
"recipientId": 456,
"pageNumber": 1,
"pageX": 50,
"pageY": 80,
"width": 20,
"height": 3
},
{
"type": "TEXT",
"recipientId": 456,
"pageNumber": 1,
"pageX": 10,
"pageY": 70,
"width": 40,
"height": 4,
"fieldMeta": {
"type": "text",
"label": "Job Title",
"placeholder": "Enter your job title",
"required": true
}
}
]
}'
````
</Tab>
<Tab value="TypeScript">
```typescript
const response = await fetch(
'https://app.documenso.com/api/v2/envelope/field/create-many',
{
method: 'POST',
headers: {
Authorization: 'api_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
documentId: 123,
fields: [
{
type: 'SIGNATURE',
recipientId: 456,
pageNumber: 1,
pageX: 10,
pageY: 80,
width: 30,
height: 5,
},
{
type: 'DATE',
recipientId: 456,
pageNumber: 1,
pageX: 50,
pageY: 80,
width: 20,
height: 3,
},
{
type: 'TEXT',
recipientId: 456,
pageNumber: 1,
pageX: 10,
pageY: 70,
width: 40,
height: 4,
fieldMeta: {
type: 'text',
label: 'Job Title',
placeholder: 'Enter your job title',
required: true,
},
},
],
}),
}
);
const { fields } = await response.json();
console.log(`Created ${fields.length} fields`);
````
</Tab>
</Tabs>
### Response
```json
{
"fields": [
{
"id": 101,
"type": "SIGNATURE",
"recipientId": 456,
"page": 1,
"positionX": 10,
"positionY": 80,
"width": 30,
"height": 5
},
{
"id": 102,
"type": "DATE",
"recipientId": 456,
"page": 1,
"positionX": 50,
"positionY": 80,
"width": 20,
"height": 3
},
{
"id": 103,
"type": "TEXT",
"recipientId": 456,
"page": 1,
"positionX": 10,
"positionY": 70,
"width": 40,
"height": 4
}
]
}
````
---
## Update Fields
Update one or more fields in a single request.
```
POST /envelope/field/update-many
```
### Request Body
| Field | Type | Required | Description |
| ------------ | ------ | -------- | ----------------------------- |
| `documentId` | number | Yes | The document ID |
| `fields` | array | Yes | Array of field update objects |
### Code Examples
<Tabs items={['curl', 'TypeScript']}>
<Tab value="curl">
```bash
curl -X POST "https://app.documenso.com/api/v2/envelope/field/update-many" \
-H "Authorization: api_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"documentId": 123,
"fields": [
{
"id": 101,
"type": "SIGNATURE",
"pageY": 85
},
{
"id": 102,
"type": "DATE",
"pageY": 85
}
]
}'
```
</Tab>
<Tab value="TypeScript">
```typescript
const response = await fetch(
'https://app.documenso.com/api/v2/envelope/field/update-many',
{
method: 'POST',
headers: {
Authorization: 'api_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
documentId: 123,
fields: [
{ id: 101, type: 'SIGNATURE', pageY: 85 },
{ id: 102, type: 'DATE', pageY: 85 },
],
}),
}
);
const { fields } = await response.json();
````
</Tab>
</Tabs>
### Response
```json
{
"fields": [
{ "id": 101, "type": "SIGNATURE", "positionY": 85 },
{ "id": 102, "type": "DATE", "positionY": 85 }
]
}
````
---
## Delete Field
Remove a field from a document.
```
POST /envelope/field/delete
```
### Request Body
| Field | Type | Required | Description |
| --------- | ------ | -------- | ------------ |
| `fieldId` | number | Yes | The field ID |
### Code Examples
<Tabs items={['curl', 'TypeScript']}>
<Tab value="curl">
```bash
curl -X POST "https://app.documenso.com/api/v2/envelope/field/delete" \
-H "Authorization: api_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"fieldId": 456
}'
```
</Tab>
<Tab value="TypeScript">
```typescript
const response = await fetch(
'https://app.documenso.com/api/v2/envelope/field/delete',
{
method: 'POST',
headers: {
Authorization: 'api_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
fieldId: 456,
}),
}
);
const { success } = await response.json();
````
</Tab>
</Tabs>
### Response
```json
{
"success": true
}
````
---
## Field Positioning
Fields use percentage-based coordinates relative to the PDF page dimensions.
| Property | Range | Description |
| ----------- | ----- | -------------------------------------------------- |
| `positionX` | 0-100 | Horizontal position from left edge (0 = left edge) |
| `positionY` | 0-100 | Vertical position from top edge (0 = top edge) |
| `width` | 0-100 | Field width as percentage of page width |
| `height` | 0-100 | Field height as percentage of page height |
| `page` | 1+ | Page number (1-indexed) |
### Coordinate System
```
(0,0) ─────────────────────────── (100,0)
│ │
│ ┌─────────┐ │
│ │ Field │ (pageX: 10, │
│ │ │ pageY: 20, │
│ └─────────┘ width: 30, │
│ height: 5) │
│ │
(0,100) ─────────────────────────(100,100)
```
### Example: Position a Signature at Bottom Right
```typescript
const field = {
type: 'SIGNATURE',
recipientId: 123,
pageNumber: 1,
pageX: 60, // 60% from left
pageY: 85, // 85% from top (near bottom)
width: 30, // 30% of page width
height: 8, // 8% of page height
};
```
---
## Placeholder-Based Field Positioning
Instead of specifying exact coordinates, you can position fields using placeholder text embedded in your PDF. Include placeholder markers such as `{{signature, r1}}` in your document, and Documenso will create fields at those locations when the document is uploaded.
This approach is useful when generating PDFs programmatically or using templates with consistent layouts.
See the [PDF Placeholders](/docs/users/documents/advanced/pdf-placeholders) guide for the full placeholder format reference, including supported field types, recipient identifiers, and field options.
---
## Field Meta Options
Each field type supports specific configuration through `fieldMeta`.
### Common Options
All field types support these base options:
| Option | Type | Description |
| ------------- | ------- | --------------------------------------- |
| `label` | string | Display text shown near the field |
| `placeholder` | string | Hint text when field is empty |
| `required` | boolean | Whether field must be completed |
| `readOnly` | boolean | Lock field with a pre-filled value |
| `fontSize` | number | Text size in pixels (8-96, default: 12) |
### Signature Field
```json
{
"type": "SIGNATURE",
"fieldMeta": {
"type": "signature",
"required": true
}
}
```
### Text Field
```json
{
"type": "TEXT",
"fieldMeta": {
"type": "text",
"label": "Company Name",
"placeholder": "Enter company name",
"text": "Default value",
"characterLimit": 100,
"textAlign": "left",
"required": true
}
}
```
| Option | Type | Description |
| ---------------- | ------ | ---------------------------------- |
| `text` | string | Default value |
| `characterLimit` | number | Maximum characters allowed |
| `textAlign` | string | `left`, `center`, or `right` |
| `lineHeight` | number | Spacing between lines (1-10) |
| `letterSpacing` | number | Spacing between characters (0-100) |
### Number Field
```json
{
"type": "NUMBER",
"fieldMeta": {
"type": "number",
"label": "Quantity",
"minValue": 1,
"maxValue": 100,
"value": "10",
"required": true
}
}
```
| Option | Type | Description |
| -------------- | ------ | --------------------- |
| `value` | string | Default value |
| `minValue` | number | Minimum allowed value |
| `maxValue` | number | Maximum allowed value |
| `numberFormat` | string | Display format |
### Date Field
```json
{
"type": "DATE",
"fieldMeta": {
"type": "date",
"textAlign": "left",
"required": true
}
}
```
### Checkbox Field
```json
{
"type": "CHECKBOX",
"fieldMeta": {
"type": "checkbox",
"label": "Agreements",
"values": [
{ "id": 1, "value": "Terms of Service", "checked": false },
{ "id": 2, "value": "Privacy Policy", "checked": false }
],
"validationRule": "min",
"validationLength": 1,
"direction": "vertical",
"required": true
}
}
```
| Option | Type | Description |
| ------------------ | ------ | --------------------------------- |
| `values` | array | List of checkbox options |
| `validationRule` | string | Validation type for selections |
| `validationLength` | number | Number for validation rule |
| `direction` | string | `vertical` or `horizontal` layout |
### Radio Field
```json
{
"type": "RADIO",
"fieldMeta": {
"type": "radio",
"label": "Payment Method",
"values": [
{ "id": 1, "value": "Credit Card", "checked": false },
{ "id": 2, "value": "Bank Transfer", "checked": true },
{ "id": 3, "value": "Check", "checked": false }
],
"direction": "vertical",
"required": true
}
}
```
### Dropdown Field
```json
{
"type": "DROPDOWN",
"fieldMeta": {
"type": "dropdown",
"label": "Country",
"values": [{ "value": "United States" }, { "value": "Canada" }, { "value": "United Kingdom" }],
"defaultValue": "United States",
"required": true
}
}
```
| Option | Type | Description |
| -------------- | ------ | ------------------------ |
| `values` | array | List of dropdown options |
| `defaultValue` | string | Pre-selected option |
---
## Complete Example
Create a document with a signature block containing multiple field types:
```typescript
async function addSignatureBlock(documentId: number, recipientId: number) {
const fields = [
// Signature
{
type: 'SIGNATURE',
recipientId,
pageNumber: 1,
pageX: 10,
pageY: 80,
width: 30,
height: 8,
fieldMeta: {
type: 'signature',
required: true,
},
},
// Printed name
{
type: 'NAME',
recipientId,
pageNumber: 1,
pageX: 10,
pageY: 90,
width: 30,
height: 4,
fieldMeta: {
type: 'name',
label: 'Printed Name',
},
},
// Date
{
type: 'DATE',
recipientId,
pageNumber: 1,
pageX: 50,
pageY: 80,
width: 20,
height: 4,
fieldMeta: {
type: 'date',
label: 'Date',
},
},
// Job title
{
type: 'TEXT',
recipientId,
pageNumber: 1,
pageX: 50,
pageY: 90,
width: 30,
height: 4,
fieldMeta: {
type: 'text',
label: 'Title',
placeholder: 'Enter your job title',
},
},
];
const response = await fetch('https://app.documenso.com/api/v2/envelope/field/create-many', {
method: 'POST',
headers: {
Authorization: 'api_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({ documentId, fields }),
});
return response.json();
}
```
---
## Error Responses
| Status | Description |
| ------ | ---------------------------------------------------- |
| `400` | Invalid field configuration or document already sent |
| `401` | Invalid or missing API key |
| `404` | Document, recipient, or field not found |
| `500` | Server error |
<Callout type="warn">
Fields cannot be modified after a document is sent for signing. Make all field changes while the
document is in `DRAFT` status.
</Callout>
---
## See Also
- [Documents API](/docs/developers/api/documents) - Create and manage documents
- [Recipients API](/docs/developers/api/recipients) - Add signers to documents
- [Field Types](/docs/concepts/field-types) - Detailed field type reference
@@ -0,0 +1,72 @@
---
title: API Reference
description: Complete reference for the Documenso REST API.
---
import { Callout } from 'fumadocs-ui/components/callout';
<Callout type="warn">
The guides below cover common API patterns but may not reflect the latest endpoints or parameters.
For an always up-to-date reference, see the [OpenAPI Reference](https://openapi.documenso.com).
</Callout>
## Base URL
```
https://app.documenso.com/api/v2
```
For self-hosted instances, replace with your instance URL.
---
## Authentication
All requests require an API key in the `Authorization` header:
```
Authorization: api_xxxxxxxxxxxxxxxx
```
<Callout type="info">
See [Authentication](/docs/developers/getting-started/authentication) for details.
</Callout>
---
## Endpoints
<Cards>
<Card
title="Documents"
description="Create, retrieve, update, and delete documents."
href="/docs/developers/api/documents"
/>
<Card
title="Recipients"
description="Manage document recipients and signers."
href="/docs/developers/api/recipients"
/>
<Card
title="Fields"
description="Add and configure signature fields."
href="/docs/developers/api/fields"
/>
<Card
title="Templates"
description="Work with document templates."
href="/docs/developers/api/templates"
/>
<Card
title="Teams"
description="Manage teams and team members."
href="/docs/developers/api/teams"
/>
</Cards>
---
## See Also
- [First API Call](/docs/developers/getting-started/first-api-call) - Quick start example
- [Webhooks](/docs/developers/webhooks) - Get notified about document events
@@ -0,0 +1,13 @@
{
"title": "API Reference",
"pages": [
"documents",
"recipients",
"fields",
"templates",
"teams",
"rate-limits",
"versioning",
"developer-mode"
]
}
@@ -0,0 +1,67 @@
---
title: Rate Limits
description: Learn about the rate limits for the Documenso Public API.
---
import { Callout } from 'fumadocs-ui/components/callout';
## Overview
Documenso enforces rate limits on all API endpoints to ensure service stability.
## HTTP Rate Limits
**Limit:** 100 requests per minute per IP address
**Response:** 429 Too Many Requests
### Rate Limit Response
```json
{
"error": "Too many requests, please try again later."
}
```
<Callout type="warn">
No rate limit headers are currently provided. When you receive a 429 response, wait at least 60
seconds before retrying.
</Callout>
## Resource Limits
Beyond HTTP rate limits, your account has usage limits based on your subscription plan.
### Plan Limits
| Resource | Free | Paid | Self-hosted | Enterprise |
| ---------------- | ---- | --------- | ----------- | ---------- |
| Documents/month | 5 | Unlimited | Unlimited | Unlimited |
| Total Recipients | 10 | Unlimited | Unlimited | Unlimited |
| Direct Templates | 3 | Unlimited | Unlimited | Unlimited |
### Error Response
When you exceed a resource limit:
```json
{
"error": "You have reached your document limit for this month. Please upgrade your plan.",
"code": "LIMIT_EXCEEDED",
"statusCode": 400
}
```
## Error Codes
| Code | Status | Description |
| ------------------- | ------ | ----------------------------- |
| `TOO_MANY_REQUESTS` | 429 | HTTP rate limit exceeded |
| `LIMIT_EXCEEDED` | 400 | Resource usage limit exceeded |
---
## See Also
- [Authentication](/docs/developers/getting-started/authentication) - API authentication guide
- [API Versioning](/docs/developers/api/versioning) - API version management
- [First API Call](/docs/developers/getting-started/first-api-call) - Getting started with the API
@@ -0,0 +1,504 @@
---
title: Recipients API
description: Add and manage envelope recipients via API.
---
import { Callout } from 'fumadocs-ui/components/callout';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
<Callout type="warn">
This guide may not reflect the latest endpoints or parameters. For an always up-to-date reference,
see the [OpenAPI Reference](https://openapi.documenso.com).
</Callout>
## Recipient Object
```json
{
"id": 123,
"envelopeId": "clu1abc2def3ghi4jkl",
"email": "signer@example.com",
"name": "John Doe",
"role": "SIGNER",
"signingOrder": 1,
"token": "abc123...",
"signedAt": "2024-01-15T10:30:00Z",
"readStatus": "OPENED",
"signingStatus": "SIGNED",
"sendStatus": "SENT"
}
```
| Field | Type | Description |
| --------------- | -------------- | ------------------------------------- |
| `id` | number | Unique recipient identifier |
| `envelopeId` | string | ID of the associated envelope |
| `email` | string | Recipient's email address |
| `name` | string | Recipient's display name |
| `role` | string | Recipient role (see below) |
| `signingOrder` | number \| null | Order in sequential signing |
| `token` | string | Unique token for signing URL |
| `signedAt` | string \| null | ISO timestamp when signed |
| `readStatus` | string | `NOT_OPENED` or `OPENED` |
| `signingStatus` | string | `NOT_SIGNED`, `SIGNED`, or `REJECTED` |
| `sendStatus` | string | `NOT_SENT` or `SENT` |
---
## Recipient Roles
| Role | Description |
| ----------- | -------------------------------------------------------------- |
| `SIGNER` | Must sign the document. Required fields must be completed. |
| `APPROVER` | Must approve the document before signers can proceed. |
| `VIEWER` | Can view the document but takes no action. |
| `CC` | Receives a copy of the completed document. No action required. |
| `ASSISTANT` | Can fill in fields on behalf of another recipient. |
---
## Get Recipient
Retrieve a single recipient by ID.
```
GET /api/v2/envelope/recipient/{recipientId}
```
### Example
<Tabs items={['curl', 'TypeScript']}>
<Tab value="curl">
```bash
curl "https://app.documenso.com/api/v2/envelope/recipient/789" \
-H "Authorization: api_xxxxxxxxxxxxxxxx"
```
</Tab>
<Tab value="TypeScript">
```typescript
const response = await fetch(
'https://app.documenso.com/api/v2/envelope/recipient/789',
{
headers: {
Authorization: 'api_xxxxxxxxxxxxxxxx',
},
},
);
const recipient = await response.json();
```
</Tab>
</Tabs>
### Response
Returns the full recipient object including fields.
---
## Create Recipients
Add one or more recipients to an envelope.
```
POST /api/v2/envelope/recipient/create-many
````
### Request Body
| Field | Type | Required | Description |
| ------------ | ------ | -------- | --------------------------------------- |
| `envelopeId` | string | Yes | ID of the envelope to add recipients to |
| `data` | array | Yes | Array of recipient objects |
Each item in the `data` array:
| Field | Type | Required | Description |
| -------------- | -------- | -------- | ------------------------------------------ |
| `email` | string | Yes | Recipient's email address |
| `name` | string | Yes | Recipient's display name (max 255 chars) |
| `role` | string | Yes | Recipient role (see Recipient Roles above) |
| `signingOrder` | number | No | Position in sequential signing |
| `accessAuth` | string[] | No | Access authentication types |
| `actionAuth` | string[] | No | Action authentication types |
### Example
<Tabs items={['curl', 'TypeScript']}>
<Tab value="curl">
```bash
curl -X POST "https://app.documenso.com/api/v2/envelope/recipient/create-many" \
-H "Authorization: api_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"envelopeId": "clu1abc2def3ghi4jkl",
"data": [
{
"email": "signer@example.com",
"name": "John Doe",
"role": "SIGNER",
"signingOrder": 1
},
{
"email": "approver@example.com",
"name": "Jane Smith",
"role": "APPROVER",
"signingOrder": 0
}
]
}'
````
</Tab>
<Tab value="TypeScript">
```typescript
const response = await fetch(
'https://app.documenso.com/api/v2/envelope/recipient/create-many',
{
method: 'POST',
headers: {
Authorization: 'api_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
envelopeId: 'clu1abc2def3ghi4jkl',
data: [
{
email: 'signer@example.com',
name: 'John Doe',
role: 'SIGNER',
signingOrder: 1,
},
{
email: 'approver@example.com',
name: 'Jane Smith',
role: 'APPROVER',
signingOrder: 0,
},
],
}),
},
);
const { data: recipients } = await response.json();
````
</Tab>
</Tabs>
### Response
```json
{
"data": [
{
"id": 789,
"envelopeId": "clu1abc2def3ghi4jkl",
"email": "signer@example.com",
"name": "John Doe",
"role": "SIGNER",
"signingOrder": 1,
"token": "abc123def456",
"signedAt": null,
"readStatus": "NOT_OPENED",
"signingStatus": "NOT_SIGNED",
"sendStatus": "NOT_SENT"
},
{
"id": 790,
"envelopeId": "clu1abc2def3ghi4jkl",
"email": "approver@example.com",
"name": "Jane Smith",
"role": "APPROVER",
"signingOrder": 0,
"token": "def456ghi789",
"signedAt": null,
"readStatus": "NOT_OPENED",
"signingStatus": "NOT_SIGNED",
"sendStatus": "NOT_SENT"
}
]
}
````
---
## Update Recipients
Update one or more recipients on an envelope. Only available for envelopes that are not yet completed.
```
POST /api/v2/envelope/recipient/update-many
```
### Request Body
| Field | Type | Required | Description |
| ------------ | ------ | -------- | -------------------------------------------- |
| `envelopeId` | string | Yes | ID of the envelope containing the recipients |
| `data` | array | Yes | Array of recipient update objects |
Each item in the `data` array:
| Field | Type | Required | Description |
| -------------- | -------- | -------- | ---------------------------------- |
| `id` | number | Yes | ID of the recipient to update |
| `email` | string | No | New email address |
| `name` | string | No | New display name (max 255 chars) |
| `role` | string | No | New recipient role |
| `signingOrder` | number | No | New position in sequential signing |
| `accessAuth` | string[] | No | Access authentication types |
| `actionAuth` | string[] | No | Action authentication types |
### Example
<Tabs items={['curl', 'TypeScript']}>
<Tab value="curl">
```bash
curl -X POST "https://app.documenso.com/api/v2/envelope/recipient/update-many" \
-H "Authorization: api_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"envelopeId": "clu1abc2def3ghi4jkl",
"data": [
{
"id": 789,
"name": "Jane Doe",
"signingOrder": 2
}
]
}'
```
</Tab>
<Tab value="TypeScript">
```typescript
const response = await fetch(
'https://app.documenso.com/api/v2/envelope/recipient/update-many',
{
method: 'POST',
headers: {
Authorization: 'api_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
envelopeId: 'clu1abc2def3ghi4jkl',
data: [
{
id: 789,
name: 'Jane Doe',
signingOrder: 2,
},
],
}),
},
);
const { data: updatedRecipients } = await response.json();
```
</Tab>
</Tabs>
### Response
Returns the updated recipient objects in a `data` array.
---
## Delete Recipient
Remove a recipient from an envelope. Only available for envelopes that are not yet completed.
```
POST /api/v2/envelope/recipient/delete
````
### Request Body
| Field | Type | Required | Description |
| ------------- | ------ | -------- | ------------------------------- |
| `recipientId` | number | Yes | ID of the recipient to remove |
### Example
<Tabs items={['curl', 'TypeScript']}>
<Tab value="curl">
```bash
curl -X POST "https://app.documenso.com/api/v2/envelope/recipient/delete" \
-H "Authorization: api_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"recipientId": 789
}'
````
</Tab>
<Tab value="TypeScript">
```typescript
const response = await fetch(
'https://app.documenso.com/api/v2/envelope/recipient/delete',
{
method: 'POST',
headers: {
Authorization: 'api_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
recipientId: 789,
}),
},
);
const result = await response.json();
// { "success": true }
````
</Tab>
</Tabs>
### Response
```json
{
"success": true
}
````
---
## Signing Order
When an envelope uses sequential signing, recipients sign in a specific order defined by `signingOrder`.
### Setting Signing Order
When creating recipients, assign `signingOrder` values to control the sequence:
```typescript
const response = await fetch('https://app.documenso.com/api/v2/envelope/recipient/create-many', {
method: 'POST',
headers: {
Authorization: 'api_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
envelopeId: 'clu1abc2def3ghi4jkl',
data: [
{
email: 'approver@example.com',
name: 'Approver',
role: 'APPROVER',
signingOrder: 0, // Approvers typically go first
},
{
email: 'first@example.com',
name: 'First Signer',
role: 'SIGNER',
signingOrder: 1,
},
{
email: 'second@example.com',
name: 'Second Signer',
role: 'SIGNER',
signingOrder: 2,
},
],
}),
});
```
<Callout type="info">
To enable sequential signing, set `signingOrder` to `SEQUENTIAL` in the envelope metadata when
creating or updating the envelope. See the [Documents API](/docs/developers/api/documents) for
details.
</Callout>
### Signing Order Behavior
- Recipients with lower `signingOrder` values sign first
- Recipients with the same `signingOrder` can sign simultaneously
- `CC` recipients receive the document after all signing is complete
- `APPROVER` recipients must approve before signers with higher order values
---
## Authentication Options
For enhanced security, you can require additional authentication when recipients access or sign a document.
### Access Authentication
Controls who can view the document:
| Type | Description |
| ----------------- | ------------------------------ |
| `ACCOUNT` | Recipient must be logged in |
| `TWO_FACTOR_AUTH` | Recipient must verify with 2FA |
### Action Authentication
Controls who can sign the document:
| Type | Description |
| ----------------- | ---------------------------------------- |
| `ACCOUNT` | Recipient must be logged in |
| `PASSKEY` | Require passkey authentication |
| `TWO_FACTOR_AUTH` | Require 2FA code |
| `PASSWORD` | Require password verification |
| `EXPLICIT_NONE` | Explicitly disable action authentication |
### Example
```typescript
const response = await fetch('https://app.documenso.com/api/v2/envelope/recipient/create-many', {
method: 'POST',
headers: {
Authorization: 'api_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
envelopeId: 'clu1abc2def3ghi4jkl',
data: [
{
email: 'signer@example.com',
name: 'John Doe',
role: 'SIGNER',
accessAuth: ['ACCOUNT'],
actionAuth: ['PASSKEY', 'TWO_FACTOR_AUTH'],
},
],
}),
});
```
---
## Error Responses
| Status | Description |
| ------ | ------------------------------------------------ |
| `400` | Invalid request body or recipient already exists |
| `400` | Envelope is already completed |
| `401` | Invalid or missing API key |
| `404` | Envelope or recipient not found |
| `500` | Server error |
### Example Error Response
```json
{
"message": "Recipient already exists"
}
```
---
## See Also
- [Documents API](/docs/developers/api/documents) - Create and manage envelopes
- [Fields API](/docs/developers/api/fields) - Add signature fields for recipients
@@ -0,0 +1,373 @@
---
title: Teams API
description: Manage team resources, documents, and templates with team-scoped API tokens.
---
import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
<Callout type="warn">
This guide may not reflect the latest endpoints or parameters. For an always up-to-date reference,
see the [OpenAPI Reference](https://openapi.documenso.com).
</Callout>
## Team Object
A team object contains the following properties:
| Property | Type | Description |
| ----------------- | -------------- | --------------------------------------------------- |
| `id` | number | Unique team identifier |
| `name` | string | Team display name |
| `url` | string | Unique team URL slug |
| `createdAt` | string | ISO 8601 timestamp |
| `avatarImageId` | string \| null | ID of the team's avatar image |
| `organisationId` | string | ID of the parent organisation |
| `currentTeamRole` | string | Your role in the team: `ADMIN`, `MANAGER`, `MEMBER` |
### Example Team Object
```json
{
"id": 123,
"name": "Engineering",
"url": "engineering",
"createdAt": "2025-01-15T10:30:00.000Z",
"avatarImageId": null,
"organisationId": "org_abc123",
"currentTeamRole": "ADMIN"
}
```
## Team-Scoped API Tokens
API tokens in Documenso are always scoped to a specific team. When you create an API token, it is associated with the team you're currently working in.
### How Team Scoping Works
- Each API token belongs to exactly one team
- All API operations using that token automatically access that team's resources
- Documents, templates, and other resources created via the API belong to the token's team
- You cannot access resources from other teams with a single token
### Creating Team-Scoped Tokens
{/* prettier-ignore */}
<Steps>
<Step>
Navigate to your team's settings
</Step>
<Step>
Go to **API Tokens**
</Step>
<Step>
Click **Create Token**
</Step>
<Step>
The token will be scoped to the current team
</Step>
</Steps>
<Callout type="info">
To work with multiple teams via API, create separate tokens for each team.
</Callout>
### Token Permissions
Your API token inherits permissions based on your role in the team:
| Role | Permissions |
| --------- | ------------------------------------------------ |
| `ADMIN` | Full access to all team resources and settings |
| `MANAGER` | Create, edit, and delete documents and templates |
| `MEMBER` | Create and manage own documents |
## Working with Team Documents
When you use a team-scoped API token, all document operations are automatically scoped to that team.
### Create a Team Document
Documents created with a team token belong to that team:
<Tabs items={['curl', 'TypeScript']}>
<Tab value="curl">
```bash
curl -X POST "https://app.documenso.com/api/v2/envelope/create" \
-H "Authorization: api_team_xxxxxxxxxxxxxxxx" \
-H "Content-Type: multipart/form-data" \
-F 'payload={
"type": "DOCUMENT",
"title": "Team Contract",
"recipients": [
{
"email": "signer@example.com",
"name": "John Smith",
"role": "SIGNER"
}
]
}' \
-F "files=@./contract.pdf;type=application/pdf"
```
</Tab>
<Tab value="TypeScript">
```typescript
const TEAM_API_TOKEN = process.env.DOCUMENSO_TEAM_API_TOKEN;
const form = new FormData();
const payload = {
type: 'DOCUMENT',
title: 'Team Contract',
recipients: [
{
email: 'signer@example.com',
name: 'John Smith',
role: 'SIGNER',
},
],
};
form.append('payload', JSON.stringify(payload));
form.append('files', fs.createReadStream('./contract.pdf'), {
contentType: 'application/pdf',
});
const response = await fetch('https://app.documenso.com/api/v2/envelope/create', {
method: 'POST',
headers: {
Authorization: TEAM_API_TOKEN,
},
body: form,
});
const { id } = await response.json();
console.log('Created team document:', id);
````
</Tab>
</Tabs>
### List Team Documents
Retrieve all documents belonging to the team:
<Tabs items={['curl', 'TypeScript']}>
<Tab value="curl">
```bash
# List all team documents
curl -X GET "https://app.documenso.com/api/v2/envelope" \
-H "Authorization: api_team_xxxxxxxxxxxxxxxx"
# Filter by status
curl -X GET "https://app.documenso.com/api/v2/envelope?status=PENDING" \
-H "Authorization: api_team_xxxxxxxxxxxxxxxx"
````
</Tab>
<Tab value="TypeScript">
```typescript
const response = await fetch('https://app.documenso.com/api/v2/envelope', {
method: 'GET',
headers: {
Authorization: TEAM_API_TOKEN,
},
});
const { data, pagination } = await response.json();
console.log(`Found ${pagination.totalItems} team documents`);
````
</Tab>
</Tabs>
## Working with Team Templates
Templates created with a team token are shared across the team.
### Create a Team Template
<Tabs items={['curl', 'TypeScript']}>
<Tab value="curl">
```bash
curl -X POST "https://app.documenso.com/api/v2/template/create" \
-H "Authorization: api_team_xxxxxxxxxxxxxxxx" \
-H "Content-Type: multipart/form-data" \
-F 'payload={
"title": "NDA Template",
"recipients": [
{
"email": "placeholder@example.com",
"name": "Signer",
"role": "SIGNER",
"fields": [
{
"identifier": 0,
"type": "SIGNATURE",
"page": 1,
"positionX": 10,
"positionY": 80,
"width": 30,
"height": 5
}
]
}
]
}' \
-F "files=@./nda-template.pdf;type=application/pdf"
````
</Tab>
<Tab value="TypeScript">
```typescript
const form = new FormData();
const payload = {
title: 'NDA Template',
recipients: [
{
email: 'placeholder@example.com',
name: 'Signer',
role: 'SIGNER',
fields: [
{
identifier: 0,
type: 'SIGNATURE',
page: 1,
positionX: 10,
positionY: 80,
width: 30,
height: 5,
},
],
},
],
};
form.append('payload', JSON.stringify(payload));
form.append('files', fs.createReadStream('./nda-template.pdf'), {
contentType: 'application/pdf',
});
const response = await fetch('https://app.documenso.com/api/v2/template/create', {
method: 'POST',
headers: {
Authorization: TEAM_API_TOKEN,
},
body: form,
});
const template = await response.json();
console.log('Created team template:', template.id);
````
</Tab>
</Tabs>
### List Team Templates
<Tabs items={['curl', 'TypeScript']}>
<Tab value="curl">
```bash
curl -X GET "https://app.documenso.com/api/v2/template" \
-H "Authorization: api_team_xxxxxxxxxxxxxxxx"
````
</Tab>
<Tab value="TypeScript">
```typescript
const response = await fetch('https://app.documenso.com/api/v2/template', {
method: 'GET',
headers: {
Authorization: TEAM_API_TOKEN,
},
});
const { data } = await response.json();
console.log('Team templates:', data);
````
</Tab>
</Tabs>
## Team Member Roles
| Role | Description |
| --------- | ------------------------------------------------------------------- |
| `ADMIN` | Full control over team settings, members, and all resources |
| `MANAGER` | Can manage documents, templates, and view team resources |
| `MEMBER` | Can create and manage their own documents within the team |
### Document Visibility
Team documents have visibility settings that control who can access them:
| Visibility | Description |
| ------------------ | ------------------------------------------------ |
| `EVERYONE` | All team members can view the document |
| `MANAGER_AND_ABOVE`| Only managers and admins can view |
| `ADMIN` | Only admins can view |
Set visibility when creating a document:
```typescript
const payload = {
type: 'DOCUMENT',
title: 'Confidential Agreement',
visibility: 'ADMIN', // Only team admins can view
recipients: [...],
};
````
## Multi-Team Workflow
To work with multiple teams, create and manage separate API tokens for each team.
### Example: Sync Documents Across Teams
```typescript
// Tokens for different teams
const SALES_TEAM_TOKEN = process.env.SALES_TEAM_API_TOKEN;
const LEGAL_TEAM_TOKEN = process.env.LEGAL_TEAM_API_TOKEN;
// Get pending documents from sales team
const salesResponse = await fetch('https://app.documenso.com/api/v2/envelope?status=PENDING', {
headers: { Authorization: SALES_TEAM_TOKEN },
});
const salesDocs = await salesResponse.json();
// Get completed documents from legal team
const legalResponse = await fetch('https://app.documenso.com/api/v2/envelope?status=COMPLETED', {
headers: { Authorization: LEGAL_TEAM_TOKEN },
});
const legalDocs = await legalResponse.json();
console.log(`Sales team: ${salesDocs.pagination.totalItems} pending`);
console.log(`Legal team: ${legalDocs.pagination.totalItems} completed`);
```
## Error Responses
| Status | Description |
| ------ | ------------------------------------------------- |
| `401` | Invalid or expired API token |
| `403` | Token doesn't have permission for this operation |
| `404` | Resource not found or not accessible by this team |
### Example Error Response
```json
{
"message": "You do not have permission to access this resource"
}
```
<Callout type="warn">
API tokens can only access resources belonging to their associated team. Attempting to access
resources from another team returns a 403 or 404 error.
</Callout>
## See Also
- [Documents API](/docs/developers/api/documents) - Create and manage documents
- [Templates API](/docs/developers/api/templates) - Work with document templates
- [Authentication](/docs/developers/getting-started/authentication) - Create and manage API tokens
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,25 @@
---
title: API Versioning
description: Versioning information for the Documenso public API.
---
import { Callout } from 'fumadocs-ui/components/callout';
## Overview
Documenso uses API versioning to manage changes to the public API. This allows us to introduce new features, fix bugs, and make other changes without breaking existing integrations.
<Callout type="info">The current version of the API is `v2`.</Callout>
The API version is specified in the URL. For example, the base URL for the `v2` API is `https://app.documenso.com/api/v2`.
We may make changes to the API without incrementing the version number. We will always try to avoid breaking changes, but in some cases, it may be necessary to make changes that are not backward compatible. In these cases, we will increment the version number and provide information about the changes in the release notes.
Also, we may deprecate certain features or endpoints in the API. When we deprecate a feature or endpoint, we will provide information about the deprecation in the release notes and give a timeline for when the feature or endpoint will be removed.
---
## See Also
- [Authentication](/docs/developers/getting-started/authentication) - API authentication guide
- [Rate Limits](/docs/developers/api/rate-limits) - API rate limit details
@@ -0,0 +1,91 @@
---
title: Contributing Translations
description: Learn how to contribute translations to Documenso and become part of our community.
---
import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';
We are always open for help with translations! Currently we utilise AI to generate the initial translations for new languages, which are then improved over time by our awesome community.
If you are looking for development notes on translations, you can find them [here](/docs/developers/local-development/translations).
<Callout type="info">
Contributions are made through GitHub Pull Requests, so you will need a GitHub account to
contribute.
</Callout>
## Overview
We store our translations in PO files, which are located in our GitHub repository [here](https://github.com/documenso/documenso/tree/main/packages/lib/translations).
The translation files are organized into folders represented by their respective language codes (`en` for English, `de` for German, etc).
Each PO file contains translations which look like this:
```po
#: apps/remix/app/(signing)/sign/[token]/no-longer-available.tsx:61
msgid "Want to send slick signing links like this one? <0>Check out Documenso.</0>"
msgstr "Möchten Sie auffällige Signatur-Links wie diesen senden? <0>Überprüfen Sie Documenso.</0>"
```
- `msgid`: The original text in English (never edit this manually)
- `msgstr`: The translated text in the target language
<Callout type="warn">
Notice the `<0>` tags? These represent HTML elements and must remain in both the `msgid` and `msgstr`. Make sure to translate the content between these tags while keeping the tags intact.
</Callout>
## How to Contribute
### Updating Existing Translations
{/* prettier-ignore */}
<Steps>
<Step>
Fork the repository
</Step>
<Step>
Navigate to the appropriate language folder and open the PO file you want to update
</Step>
<Step>
Make your changes, ensuring you follow the PO file format
</Step>
<Step>
Commit your changes with a message such as <code>chore: update German translations</code>
</Step>
<Step>
Create a Pull Request
</Step>
</Steps>
### Adding a New Language
If you want to add translations for a language that doesn't exist yet:
{/* prettier-ignore */}
<Steps>
<Step>
Create an issue in our GitHub repository requesting the addition of the new language
</Step>
<Step>
Wait for our team to review and approve the request
</Step>
<Step>
Once approved, we will set up the necessary files and kickstart the translations with AI to
provide initial coverage
</Step>
</Steps>
## Need Help?
<Callout type="info">
If you have any questions, hop into our [Discord](https://documen.so/discord) and ask us directly!
</Callout>
Thank you for helping make Documenso more accessible to users around the world!
## See Also
- [Translations (Development)](/docs/developers/local-development/translations) - Technical guide to translations in code
- [Contributing Guide](/docs/developers/contributing) - General contributing guidelines
@@ -0,0 +1,152 @@
---
title: Contributing to Documenso
description: Learn how to contribute to Documenso and become part of our community.
---
import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';
## Overview
If you plan to contribute to Documenso, please take a moment to feel awesome. People like you are what open source is about. Any contributions, no matter how big or small, are highly appreciated.
This guide will help you get started with contributing to Documenso.
## Before Getting Started
{/* prettier-ignore */}
<Steps>
<Step>
### Check the existing issues and pull requests
Search the existing [issues](https://github.com/documenso/documenso/issues) to see if someone else reported the same issue. Or, check the [existing PRs](https://github.com/documenso/documenso/pulls) to see if someone else is already working on the same thing.
</Step>
<Step>
### Creating a new issue
If there is no issue or PR for the problem you are facing, feel free to create a new issue. Make sure to provide as much detail as possible, including the steps to reproduce the issue.
</Step>
<Step>
### Picking an existing issue
If you pick an existing issue, take into consideration the discussion on the issue.
</Step>
<Step>
### Contributor license agreement
Accept the [Contributor License Agreement](https://documen.so/cla) to ensure we can accept your contributions.
</Step>
</Steps>
## Taking Issues
Before taking an issue, ensure that:
- The issue has been assigned the public label.
- The issue is clearly defined and understood.
- No one has been assigned to the issue.
- No one has expressed the intention to work on it.
After that:
1. Comment on the issue with your intention to work on it.
2. Start working on the issue.
Feel free to ask for help, clarification or guidance if needed. We are here to help you.
## Developing
The development branch is `main`, and all pull requests should be made against this branch. Here's how you can get started with developing:
{/* prettier-ignore */}
<Steps>
<Step>
### Set up Documenso locally
To set up your local environment, check out the [local development](/docs/developers/local-development) guide.
</Step>
<Step>
### Pick a task
Find an issue to work on or create a new one.
> Before working on an issue, ensure that no one else is working on it. If no one is assigned to the issue, you can pick it up by leaving a comment and asking to assign it to you.
Before creating a new issue, check the existing issues to see if someone else has already reported it.
</Step>
<Step>
### Create a new branch
After you're assigned an issue, you can start working on it. Create a new branch for your feature or bug fix.
When creating a branch, make sure that the branch name:
- starts with the correct prefix: `feat/` for new features, `fix/` for bug fixes, etc.
- includes the issue ID you are working on (if applicable).
- is descriptive.
```sh
git checkout -b feat/issue-id-your-branch-name
## Example
git checkout -b feat/1234-add-share-button-to-articles
```
In the pull request description, include `references #yyyy` or `fixes #yyyy` to link it to the issue you are working on.
</Step>
<Step>
### Implement your changes
Start working on the issue you picked up and implement the changes. Make sure to test your changes locally and ensure that they work as expected.
</Step>
<Step>
### Open a pull request
After implementing your changes, open a pull request against the `main` branch.
</Step>
</Steps>
<Callout type="info">
If you need help getting started, [join us on Discord](https://documen.so/discord).
</Callout>
## Building
Before pushing code or creating pull requests, please ensure you can successfully create a successful production build. You can build the project by running the following command in your terminal:
```bash
npm run build
```
Once the project builds successfully, you can push your code changes or create a pull request.
<Callout type="info">
Remember to run tests and perform any necessary checks before finalizing your changes. As a
result, we can collaborate more effectively and maintain a high standard of code quality in our
project.
</Callout>
## See Also
- [Local Development](/docs/developers/local-development) - Set up your development environment
- [Contributing Translations](/docs/developers/contributing/contributing-translations) - Help translate Documenso
@@ -0,0 +1,4 @@
{
"title": "Contributing",
"pages": ["contributing-translations"]
}
@@ -0,0 +1,64 @@
---
title: Demo Environment
description: Use the demo environment to try out the Documenso platform and its features.
---
import { Step, Steps } from 'fumadocs-ui/components/steps';
## Overview
The demo (staging) environment is a sandbox environment that replicates the production environment. It has the same features and capabilities as the production environment, but is intended for development and testing purposes.
You can use it to try out the Documenso platform and its features before committing to a paid plan.
## How to Use the Demo Environment
{/* prettier-ignore */}
<Steps>
<Step>
### Navigate to the staging environment
Go to the [staging environment](https://stg-app.documenso.com).
</Step>
<Step>
### Create an account
You need to create a new account for the demo environment. You can't use your production account.
</Step>
<Step>
### Pick a paid plan
Choose the appropriate plan for your needs.
You can also use the free plan but it's limited to 5 documents per month and up to 10 recipients per document.
Whatever plan you choose, you can upgrade later.
</Step>
<Step>
### Use a test card
To upgrade to a paid plan, you can use a test card. Example:
```
Card number: 4242 4242 4242 4242
Expiry date: 02/2030 (or any valid future date)
CVV: 123
```
</Step>
<Step>
### Use the platform
You can then try out the platform and its features.
</Step>
<Step>
### Issues, questions and feedback
If you have any issues, questions or feedback, please reach out to us on the [Documenso Discord](https://documen.so/discord) or [GitHub](https://github.com/documenso/documenso/issues).
</Step>
</Steps>
@@ -0,0 +1,306 @@
---
title: Authoring
description: Embed document and template creation directly in your application.
---
import { Callout } from 'fumadocs-ui/components/callout';
In addition to embedding signing, Documenso supports embedded authoring. It allows your users to create and edit documents and templates without leaving your application.
<Callout type="warn">
Embedded authoring is included with [Enterprise](https://documen.so/enterprise-cta) plans. It is
also available as a paid add-on for the [Platform Plan](https://documen.so/platform-cta-pricing).
Contact sales for access.
</Callout>
## Components
The SDK provides four authoring components:
| Component | Purpose |
| ----------------------- | ----------------------- |
| `EmbedCreateDocumentV1` | Create new documents |
| `EmbedCreateTemplateV1` | Create new templates |
| `EmbedUpdateDocumentV1` | Edit existing documents |
| `EmbedUpdateTemplateV1` | Edit existing templates |
All authoring components require a **presign token** for authentication instead of a regular token.
---
## Presign Tokens
Before using any authoring component, obtain a presign token from your backend:
```
POST /api/v2/embedding/create-presign-token
```
This endpoint requires your Documenso API key. The token has a default expiration of 1 hour.
See the [API documentation](https://openapi.documenso.com/reference#tag/embedding) for full details.
<Callout type="warn">
Presign tokens should be created server-side. Never expose your API key in client-side code.
</Callout>
---
## Creating Documents
```jsx
import { EmbedCreateDocumentV1 } from '@documenso/embed-react';
const DocumentCreator = ({ presignToken }) => {
return (
<div style={{ height: '800px', width: '100%' }}>
<EmbedCreateDocumentV1
presignToken={presignToken}
externalId="order-12345"
onDocumentCreated={(data) => {
console.log('Document created:', data.documentId);
console.log('External ID:', data.externalId);
}}
/>
</div>
);
};
```
---
## Creating Templates
```jsx
import { EmbedCreateTemplateV1 } from '@documenso/embed-react';
const TemplateCreator = ({ presignToken }) => {
return (
<div style={{ height: '800px', width: '100%' }}>
<EmbedCreateTemplateV1
presignToken={presignToken}
externalId="template-12345"
onTemplateCreated={(data) => {
console.log('Template created:', data.templateId);
}}
/>
</div>
);
};
```
---
## Editing Documents
```jsx
import { EmbedUpdateDocumentV1 } from '@documenso/embed-react';
const DocumentEditor = ({ presignToken, documentId }) => {
return (
<div style={{ height: '800px', width: '100%' }}>
<EmbedUpdateDocumentV1
presignToken={presignToken}
documentId={documentId}
externalId="order-12345"
onDocumentUpdated={(data) => {
console.log('Document updated:', data.documentId);
}}
/>
</div>
);
};
```
---
## Editing Templates
```jsx
import { EmbedUpdateTemplateV1 } from '@documenso/embed-react';
const TemplateEditor = ({ presignToken, templateId }) => {
return (
<div style={{ height: '800px', width: '100%' }}>
<EmbedUpdateTemplateV1
presignToken={presignToken}
templateId={templateId}
externalId="template-12345"
onTemplateUpdated={(data) => {
console.log('Template updated:', data.templateId);
}}
/>
</div>
);
};
```
---
## Props
### All Authoring Components
| Prop | Type | Required | Description |
| ------------------ | --------- | -------- | -------------------------------------------------------- |
| `presignToken` | `string` | Yes | Authentication token from your backend |
| `externalId` | `string` | No | Your reference ID to link with the document or template |
| `host` | `string` | No | Custom host URL. Defaults to `https://app.documenso.com` |
| `css` | `string` | No | Custom CSS string (Platform Plan) |
| `cssVars` | `object` | No | CSS variable overrides (Platform Plan) |
| `darkModeDisabled` | `boolean` | No | Disable dark mode (Platform Plan) |
| `className` | `string` | No | CSS class for the iframe |
| `features` | `object` | No | Feature toggles for the authoring experience |
### Update Components Only
| Prop | Type | Required | Description |
| ---------------- | --------- | -------- | ---------------------------------------------------------- |
| `documentId` | `number` | Yes | The document ID to edit (for document update component) |
| `templateId` | `number` | Yes | The template ID to edit (for template update component) |
| `onlyEditFields` | `boolean` | No | Restrict editing to fields only, skipping recipient config |
---
## Feature Toggles
Customize what options are available in the authoring experience:
```jsx
<EmbedCreateDocumentV1
presignToken={presignToken}
features={{
allowConfigureSignatureTypes: true,
allowConfigureLanguage: true,
allowConfigureDateFormat: true,
allowConfigureTimezone: true,
allowConfigureRedirectUrl: true,
allowConfigureCommunication: true,
}}
/>
```
---
## Event Callbacks
All creation callbacks receive:
| Field | Type | Description |
| ---------------------------- | -------- | --------------------------------------- |
| `documentId` or `templateId` | `number` | The ID of the created or updated item |
| `externalId` | `string` | Your external reference ID, if provided |
---
## Field-Only Editing
Restrict users to editing fields only, skipping recipient configuration:
```jsx
<EmbedUpdateDocumentV1
presignToken={presignToken}
documentId={123}
onlyEditFields={true}
onDocumentUpdated={(data) => {
console.log('Fields updated:', data.documentId);
}}
/>
```
---
## Complete Integration Example
This example shows a full workflow where users create a document and then edit it:
```tsx
import { useState } from 'react';
import { EmbedCreateDocumentV1, EmbedUpdateDocumentV1 } from '@documenso/embed-react';
const DocumentManager = ({ presignToken }) => {
const [documentId, setDocumentId] = useState(null);
const [mode, setMode] = useState('create');
if (mode === 'success') {
return (
<div>
<h2>Document updated successfully</h2>
<button
onClick={() => {
setDocumentId(null);
setMode('create');
}}
>
Create Another Document
</button>
</div>
);
}
if (mode === 'edit' && documentId) {
return (
<div style={{ height: '800px', width: '100%' }}>
<button onClick={() => setMode('create')}>Back to Create</button>
<EmbedUpdateDocumentV1
presignToken={presignToken}
documentId={documentId}
onDocumentUpdated={(data) => {
console.log('Document updated:', data.documentId);
setMode('success');
}}
/>
</div>
);
}
return (
<div style={{ height: '800px', width: '100%' }}>
<EmbedCreateDocumentV1
presignToken={presignToken}
features={{
allowConfigureSignatureTypes: true,
allowConfigureLanguage: true,
}}
onDocumentCreated={(data) => {
console.log('Document created:', data.documentId);
setDocumentId(data.documentId);
setMode('edit');
}}
/>
</div>
);
};
```
---
## Additional Props
Pass extra props to the iframe for testing experimental features:
```jsx
<EmbedCreateDocumentV1
presignToken="YOUR_PRESIGN_TOKEN"
additionalProps={{
experimentalFeature: true,
customSetting: 'value',
}}
/>
```
<Callout type="info">
Presign tokens expire after 1 hour by default. You can customize this duration based on your
security requirements. Generate fresh tokens for each session and avoid caching them on the client
side.
</Callout>
---
## See Also
- [Embedding Overview](/docs/developers/embedding) - Signing embed concepts and props
- [CSS Variables](/docs/developers/embedding/css-variables) - Customize appearance
- [Documents API](/docs/developers/api/documents) - Create documents via API
- [Templates API](/docs/developers/api/templates) - Create templates via API
@@ -0,0 +1,215 @@
---
title: CSS Variables
description: Customize the appearance of embedded signing experiences with CSS variables and class targets.
---
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
import { Callout } from 'fumadocs-ui/components/callout';
<Callout type="info">
Custom CSS and CSS variables are available on the [Platform
Plan](https://documen.so/platform-cta-pricing).
</Callout>
## CSS Variables
Use the `cssVars` prop on any embed component to override default colors, spacing, and more.
```jsx
<EmbedDirectTemplate
token="your-token"
cssVars={{
background: '#ffffff',
foreground: '#000000',
primary: '#0000ff',
primaryForeground: '#ffffff',
radius: '0.5rem',
}}
/>
```
### Colors
| Variable | Description |
| ----------------------- | --------------------------------- |
| `background` | Base background color |
| `foreground` | Base text color |
| `muted` | Muted/subtle background color |
| `mutedForeground` | Muted/subtle text color |
| `popover` | Popover/dropdown background color |
| `popoverForeground` | Popover/dropdown text color |
| `card` | Card background color |
| `cardBorder` | Card border color |
| `cardBorderTint` | Card border tint/highlight color |
| `cardForeground` | Card text color |
| `fieldCard` | Field card background color |
| `fieldCardBorder` | Field card border color |
| `fieldCardForeground` | Field card text color |
| `widget` | Widget background color |
| `widgetForeground` | Widget text color |
| `border` | Default border color |
| `input` | Input field border color |
| `primary` | Primary action/button color |
| `primaryForeground` | Primary action/button text color |
| `secondary` | Secondary action/button color |
| `secondaryForeground` | Secondary button text color |
| `accent` | Accent/highlight color |
| `accentForeground` | Accent/highlight text color |
| `destructive` | Destructive/danger action color |
| `destructiveForeground` | Destructive/danger text color |
| `ring` | Focus ring color |
| `warning` | Warning/alert color |
### Spacing
| Variable | Description |
| -------- | ---------------------------------- |
| `radius` | Border radius size (e.g. `0.5rem`) |
### Framework Usage
Pass `cssVars` to any embed component. The syntax varies by framework:
```jsx
// React / Preact
<EmbedDirectTemplate token={token} cssVars={cssVars} />
// Vue
<EmbedDirectTemplate :token="token" :cssVars="cssVars" />
// Svelte
<EmbedDirectTemplate {token} cssVars={cssVars} />
// Solid
<EmbedDirectTemplate token={token} cssVars={cssVars} />
```
### Color Formats
Colors can be specified in any valid CSS format:
- Hex: `#ff0000`
- RGB: `rgb(255, 0, 0)`
- HSL: `hsl(0, 100%, 50%)`
- Named: `red`
---
## Custom CSS
Use the `css` prop to inject a CSS string for more targeted control:
```jsx
<EmbedDirectTemplate
token="your-token"
css={`
.documenso-embed {
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
`}
/>
```
---
## CSS Class Targets
Specific parts of the embed can be targeted with CSS classes for granular styling.
### Component Classes
| Class | Description |
| --------------------------------- | --------------------------------------------- |
| `.embed--Root` | Main container for the embedded experience |
| `.embed--DocumentContainer` | Container for the document and signing widget |
| `.embed--DocumentViewer` | Container for the document viewer |
| `.embed--DocumentWidget` | The signing widget container |
| `.embed--DocumentWidgetContainer` | Outer container for the signing widget |
| `.embed--DocumentWidgetHeader` | Header section of the signing widget |
| `.embed--DocumentWidgetContent` | Main content area of the signing widget |
| `.embed--DocumentWidgetForm` | Form section within the signing widget |
| `.embed--DocumentWidgetFooter` | Footer section of the signing widget |
| `.embed--WaitingForTurn` | Waiting screen when it is not the user's turn |
| `.embed--DocumentCompleted` | Completion screen after signing |
| `.field--FieldRootContainer` | Base container for document fields |
### Field Data Attributes
Fields expose data attributes for state-based styling:
| Attribute | Values | Description |
| ------------------- | ---------------------------------------------- | ------------------------------------ |
| `[data-field-type]` | `SIGNATURE`, `TEXT`, `CHECKBOX`, `RADIO`, etc. | The type of field |
| `[data-inserted]` | `true`, `false` | Whether the field has been filled |
| `[data-validate]` | `true`, `false` | Whether the field is being validated |
### Example
```css
/* Style signature fields */
.field--FieldRootContainer[data-field-type='SIGNATURE'] {
background-color: rgba(0, 0, 0, 0.02);
}
/* Style filled fields */
.field--FieldRootContainer[data-inserted='true'] {
background-color: var(--primary);
opacity: 0.2;
}
/* Custom widget styling */
.embed--DocumentWidget {
background-color: #ffffff;
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
}
```
### Additional Examples
```css
/* Style all field containers with transitions */
.field--FieldRootContainer {
transition: all 200ms ease;
}
/* Custom styles for the waiting screen */
.embed--WaitingForTurn {
background-color: #f9fafb;
padding: 2rem;
}
/* Responsive adjustments for the document container */
@media (min-width: 768px) {
.embed--DocumentContainer {
gap: 2rem;
}
}
```
---
## Best Practices
<Accordions type="multiple">
<Accordion title="Maintain contrast">
Ensure sufficient contrast between background and foreground colors for accessibility.
</Accordion>
<Accordion title="Test dark mode">
If dark mode is not disabled, verify your variables work in both modes.
</Accordion>
<Accordion title="Match your brand">
Align `primary` and `accent` colors with your brand for a cohesive look.
</Accordion>
<Accordion title="Consistent radius">
Use a border radius that matches your application's design system.
</Accordion>
</Accordions>
---
## See Also
- [Embedding Overview](/docs/developers/embedding) - Props reference and concepts
- [React](/docs/developers/embedding/sdks/react) - React SDK usage
- [Vue](/docs/developers/embedding/sdks/vue) - Vue SDK usage
@@ -0,0 +1,514 @@
---
title: Direct Links
description: Share a URL or embed an iframe to let users sign documents without email invitations.
---
import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
## What Are Direct Links?
Direct links are unique URLs tied to a template that allow anyone to:
{/* prettier-ignore */}
<Steps>
<Step>
View and sign a document without receiving an email invitation
</Step>
<Step>
Enter their own name and email address
</Step>
<Step>
Complete signature fields and submit the document
</Step>
</Steps>
When someone uses a direct link, Documenso creates a new document from the template with that person as the signer.
### When to Use Direct Links
- Collecting signatures from unknown recipients (forms, waivers, petitions)
- Embedding signing in your website or application
- Self-service contracts where customers initiate signing
- Public-facing agreements that anyone can sign
### Limitations
- Only work with templates, not individual documents
- Each link is tied to one recipient role in the template
- Recipients enter their own information (you cannot prefill recipient details)
## Creating Direct Link Templates
Before embedding, you need a template with direct links enabled.
### Via the Dashboard
{/* prettier-ignore */}
<Steps>
<Step>
Go to **Templates** and create or select a template
![Team templates](/embedding/team-templates.png)
</Step>
<Step>
Click the three-dot menu and select **Direct link**
</Step>
<Step>
Click **Enable direct link signing**
</Step>
<Step>
Choose which recipient in your template will use the direct link
![Enable direct link](/embedding/enable-direct-link.png)
</Step>
<Step>
Copy the generated URL
</Step>
</Steps>
### Via the API
Create a direct link for an existing template:
<Tabs items={['curl', 'TypeScript']}>
<Tab value="curl">
```bash
# Create direct link for template
curl -X POST "https://app.documenso.com/api/v2/template/direct/create" \
-H "Authorization: api_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"templateId": 123,
"directRecipientId": 1
}'
```
</Tab>
<Tab value="TypeScript">
```typescript
const API_TOKEN = process.env.DOCUMENSO_API_TOKEN;
const BASE_URL = 'https://app.documenso.com/api/v2';
const response = await fetch(`${BASE_URL}/template/direct/create`, {
method: 'POST',
headers: {
Authorization: API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({
templateId: 123,
directRecipientId: 1, // Optional: specific recipient to use
}),
});
const directLink = await response.json();
console.log('Direct link token:', directLink.token);
// URL: https://app.documenso.com/d/{token}
````
</Tab>
</Tabs>
### Response
```json
{
"id": 456,
"token": "abc123xyz",
"templateId": 123,
"directTemplateRecipientId": 1,
"enabled": true,
"createdAt": "2025-01-15T10:00:00.000Z"
}
````
The direct link URL format is:
```
https://app.documenso.com/d/{token}
```
![Copy recipient token](/embedding/copy-recipient-token.png)
---
## Embedding in an iframe
Embed the signing experience directly in your application using an iframe.
### Basic iframe Embedding
```html
<iframe
src="https://app.documenso.com/embed/direct/abc123xyz"
width="100%"
height="800"
frameborder="0"
allow="clipboard-write"
></iframe>
```
### Responsive iframe
```html
<div style="position: relative; width: 100%; padding-bottom: 75%; height: 0; overflow: hidden;">
<iframe
src="https://app.documenso.com/embed/direct/abc123xyz"
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none;"
allow="clipboard-write"
></iframe>
</div>
```
### React Component Example
```tsx
function DocumentSigner({ token }: { token: string }) {
return (
<div className="h-[800px] w-full">
<iframe
src={`https://app.documenso.com/embed/direct/${token}`}
className="h-full w-full border-0"
allow="clipboard-write"
title="Sign Document"
/>
</div>
);
}
```
<Callout type="info">
The embed URL uses `/embed/direct/{token}` instead of `/d/{token}`. The embed version is optimized
for iframe embedding with reduced navigation.
</Callout>
---
## Embedding with Redirect
Instead of an iframe, redirect users to the signing page and bring them back after completion.
### Simple Redirect
```typescript
function redirectToSigning(token: string) {
window.location.href = `https://app.documenso.com/d/${token}`;
}
```
### With Return URL
Configure a redirect URL in your template settings to return users to your application after signing:
{/* prettier-ignore */}
<Steps>
<Step>
Edit your template
</Step>
<Step>
Go to **Advanced Settings**
</Step>
<Step>
Set **Redirect URL** to your desired return URL (e.g., `https://yourapp.com/signed`)
</Step>
</Steps>
Or set it via API when creating the template:
```typescript
const response = await fetch(`${BASE_URL}/template/update`, {
method: 'POST',
headers: {
Authorization: API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({
templateId: 123,
meta: {
redirectUrl: 'https://yourapp.com/signing-complete',
},
}),
});
```
After signing completes, users are automatically redirected to the specified URL.
---
## URL Parameters
Pass additional data through the direct link URL using query parameters.
### External ID
Track which document belongs to which transaction in your system:
```
https://app.documenso.com/d/abc123xyz?externalId=order-12345
```
The external ID is stored with the created document and included in webhook payloads.
### Example: Dynamic External ID
```typescript
function getSigningUrl(token: string, orderId: string) {
const params = new URLSearchParams({
externalId: orderId,
});
return `https://app.documenso.com/d/${token}?${params.toString()}`;
}
// Usage
const url = getSigningUrl('abc123xyz', 'order-12345');
// https://app.documenso.com/d/abc123xyz?externalId=order-12345
```
### Embed URL Parameters
The embed URL supports the same parameters:
```
https://app.documenso.com/embed/direct/abc123xyz?externalId=order-12345
```
---
## Security Considerations
### Access Authentication
Direct links can require authentication before signing. Configure this in your template settings:
| Auth Type | Description |
| --------- | ---------------------------------------------- |
| None | Anyone with the link can sign |
| Account | Signer must be logged into a Documenso account |
To require authentication, set `globalAccessAuth` when creating or updating the template:
```typescript
const response = await fetch(`${BASE_URL}/template/update`, {
method: 'POST',
headers: {
Authorization: API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({
templateId: 123,
data: {
globalAccessAuth: ['ACCOUNT'], // Require login
},
}),
});
```
### Disabling Direct Links
Temporarily disable a direct link without deleting it:
<Tabs items={['curl', 'TypeScript']}>
<Tab value="curl">
```bash
curl -X POST "https://app.documenso.com/api/v2/template/direct/toggle" \
-H "Authorization: api_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"templateId": 123,
"enabled": false
}'
```
</Tab>
<Tab value="TypeScript">
```typescript
const response = await fetch(`${BASE_URL}/template/direct/toggle`, {
method: 'POST',
headers: {
Authorization: API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({
templateId: 123,
enabled: false,
}),
});
```
</Tab>
</Tabs>
Re-enable later by setting `enabled: true`. The URL remains the same.
### Deleting Direct Links
Permanently remove a direct link:
```bash
curl -X POST "https://app.documenso.com/api/v2/template/direct/delete" \
-H "Authorization: api_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"templateId": 123
}'
```
<Callout type="warn">
Deleting a direct link invalidates the URL permanently. If you enable direct links again, a new
URL will be generated.
</Callout>
---
## Styling and Customization
### Branding
The signing experience uses your organisation's branding settings:
- Logo
- Primary color
- Email customization
Configure branding in **Settings** > **Branding** or via team settings.
### Signature Options
Control which signature input methods are available:
```typescript
const response = await fetch(`${BASE_URL}/template/update`, {
method: 'POST',
headers: {
Authorization: API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({
templateId: 123,
meta: {
typedSignatureEnabled: true, // Allow typed signatures
drawSignatureEnabled: true, // Allow drawn signatures
uploadSignatureEnabled: false, // Disable uploaded signatures
},
}),
});
```
### White-labeling
Enterprise plans support white-label embedding with the "Powered by Documenso" badge removed. [Contact sales](mailto:support@documenso.com) for details.
---
## Handling Completion
### Webhook Notifications
Set up webhooks to receive notifications when documents are signed:
```json
{
"event": "DOCUMENT_SIGNED",
"payload": {
"id": 123,
"externalId": "order-12345",
"title": "contract.pdf",
"status": "COMPLETED",
"Recipient": [
{
"email": "signer@example.com",
"name": "John Doe",
"signingStatus": "SIGNED",
"signedAt": "2024-01-15T10:30:00.000Z"
}
]
},
"createdAt": "2024-01-15T10:30:00.000Z",
"webhookEndpoint": "https://your-endpoint.com/webhook"
}
```
See [Webhooks](/docs/developers/webhooks) for setup instructions.
### Polling for Status
If webhooks aren't suitable, poll the document status:
```typescript
async function checkDocumentStatus(externalId: string) {
const response = await fetch(`${BASE_URL}/envelope?source=TEMPLATE_DIRECT_LINK`, {
headers: { Authorization: API_TOKEN },
});
const { data } = await response.json();
return data.find((doc) => doc.externalId === externalId);
}
```
---
## Complete Example
This example shows a complete workflow for embedding direct link signing:
```typescript
const API_TOKEN = process.env.DOCUMENSO_API_TOKEN;
const BASE_URL = 'https://app.documenso.com/api/v2';
// 1. Get template with direct link
async function getDirectLinkTemplate(templateId: number) {
const response = await fetch(`${BASE_URL}/template/${templateId}`, {
headers: { Authorization: API_TOKEN },
});
return response.json();
}
// 2. Create direct link if not exists
async function ensureDirectLink(templateId: number, recipientId: number) {
const template = await getDirectLinkTemplate(templateId);
if (template.directLink?.enabled) {
return template.directLink.token;
}
const response = await fetch(`${BASE_URL}/template/direct/create`, {
method: 'POST',
headers: {
Authorization: API_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify({
templateId,
directRecipientId: recipientId,
}),
});
const directLink = await response.json();
return directLink.token;
}
// 3. Generate signing URL with tracking
function getEmbedUrl(token: string, orderId: string) {
const params = new URLSearchParams({ externalId: orderId });
return `https://app.documenso.com/embed/direct/${token}?${params}`;
}
// Usage
const token = await ensureDirectLink(123, 1);
const embedUrl = getEmbedUrl(token, 'order-12345');
// Embed in your page
document.getElementById('signer-frame').src = embedUrl;
```
---
## See Also
- [SDKs](/docs/developers/embedding/sdks) - Framework SDK integration
- [Templates API](/docs/developers/api/templates) - Template management
- [Webhooks](/docs/developers/webhooks) - Event notifications
- [Direct Links (User Guide)](/docs/users/documents/direct-links) - End-user documentation
@@ -0,0 +1,242 @@
---
title: Embedding
description: Embed document signing experiences directly in your application using official SDKs.
---
import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';
## Embedded Signing vs Embedded Authoring
Documenso offers two types of embedding:
- **Embedded Signing** lets you embed the signing experience in your application. Your users sign documents without leaving your site. Available on Teams Plan and above.
- **Embedded Authoring** lets you embed document and template _creation and editing_ in your application. This is an [Enterprise](/docs/policies/enterprise-edition) feature (also available as a Platform Plan add-on). See the [Authoring](/docs/developers/embedding/authoring) guide.
This page covers **embedded signing**. If you need your users to create or edit documents inside your app, see [Authoring](/docs/developers/embedding/authoring).
---
## Availability
Embedding is available on **Teams Plan** and above, as well as for **Early Adopters** within a team (Early Adopters can create a team for free).
The [Platform Plan](https://documen.so/platform-cta-pricing) adds enhanced customization:
- Custom CSS and styling variables
- Dark mode controls
- Removal of Documenso branding
---
## How It Works
There are two ways to embed signing, each using a different component and token type.
### Direct Templates
Direct templates are evergreen - each time a user completes signing, a new document is created from the template. This is the recommended approach for most use cases.
Use the `EmbedDirectTemplate` component with a template token:
```jsx
import { EmbedDirectTemplate } from '@documenso/embed-react';
<EmbedDirectTemplate
token="your-template-token"
onDocumentCompleted={(data) => {
console.log('Signed:', data.documentId);
}}
/>;
```
### Signing Tokens
For advanced integrations where you create documents via the API, you can embed the signing experience for a specific recipient using their signing token.
Use the `EmbedSignDocument` component with the recipient's token:
```jsx
import { EmbedSignDocument } from '@documenso/embed-react';
<EmbedSignDocument
token="recipient-signing-token"
onDocumentCompleted={(data) => {
console.log('Signed:', data.documentId);
}}
/>;
```
<Callout type="info">
For most use cases, direct templates are the way to go. Use signing tokens when you need
programmatic control over document creation via the API.
</Callout>
---
## Getting Your Token
### Direct Template Token
{/* prettier-ignore */}
<Steps>
<Step>
Navigate to your team's templates in Documenso
![Team Templates](/embedding/team-templates.png)
</Step>
<Step>
Click on a direct link template to copy its URL. The token is the last segment of the URL.
For example, `https://app.documenso.com/d/-WoSwWVT-fYOERS2MI37k` has the token `-WoSwWVT-fYOERS2MI37k`.
If your template is not a direct link template yet, select **Direct Link** from the three-dot menu on the templates table to enable it.
![Enable Direct Link Template](/embedding/enable-direct-link.png)
</Step>
</Steps>
### Signing Token
Signing tokens are returned in API responses when distributing a document. You can also get one manually by hovering over a recipient's avatar on a document you own and clicking their email.
![Copy Recipient Token](/embedding/copy-recipient-token.png)
---
## Framework SDKs
Pick your framework to get started:
<Cards>
<Card
title="React"
description="@documenso/embed-react"
href="/docs/developers/embedding/sdks/react"
/>
<Card title="Vue" description="@documenso/embed-vue" href="/docs/developers/embedding/sdks/vue" />
<Card
title="Svelte"
description="@documenso/embed-svelte"
href="/docs/developers/embedding/sdks/svelte"
/>
<Card
title="Angular"
description="@documenso/embed-angular"
href="/docs/developers/embedding/sdks/angular"
/>
<Card
title="Solid"
description="@documenso/embed-solid"
href="/docs/developers/embedding/sdks/solid"
/>
<Card
title="Preact"
description="@documenso/embed-preact"
href="/docs/developers/embedding/sdks/preact"
/>
</Cards>
A [Web Components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components) SDK (`@documenso/embed-webcomponent`) is also available for use outside of JavaScript frameworks. It works in any environment that supports custom elements.
If you prefer not to use any SDK, you can embed signing using [Direct Links](/docs/developers/embedding/direct-links) with a plain iframe or redirect.
---
## Props
### EmbedDirectTemplate
| Prop | Type | Description |
| --------------------- | ---------- | ---------------------------------------------------------------- |
| `token` | `string` | **Required.** The direct template token. |
| `host` | `string` | Documenso instance URL. Defaults to `https://app.documenso.com`. |
| `name` | `string` | Pre-fill the signer's name. |
| `lockName` | `boolean` | Prevent the signer from changing their name. |
| `email` | `string` | Pre-fill the signer's email. |
| `lockEmail` | `boolean` | Prevent the signer from changing their email. |
| `externalId` | `string` | Your reference ID, stored with the created document. |
| `css` | `string` | Custom CSS string (Platform Plan). |
| `cssVars` | `object` | CSS variable overrides for theming (Platform Plan). |
| `darkModeDisabled` | `boolean` | Disable dark mode in the embed (Platform Plan). |
| `onDocumentReady` | `function` | Called when the document is loaded and ready. |
| `onDocumentCompleted` | `function` | Called when signing is completed. |
| `onDocumentError` | `function` | Called when an error occurs. |
| `onFieldSigned` | `function` | Called when a field is signed. |
| `onFieldUnsigned` | `function` | Called when a field value is cleared. |
### EmbedSignDocument
| Prop | Type | Description |
| --------------------- | ---------- | ---------------------------------------------------------------- |
| `token` | `string` | **Required.** The recipient's signing token. |
| `host` | `string` | Documenso instance URL. Defaults to `https://app.documenso.com`. |
| `name` | `string` | Pre-fill the signer's name. |
| `lockName` | `boolean` | Prevent the signer from changing their name. |
| `onDocumentReady` | `function` | Called when the document is loaded and ready. |
| `onDocumentCompleted` | `function` | Called when signing is completed. |
| `onDocumentError` | `function` | Called when an error occurs. |
---
## Event Callbacks
### onDocumentCompleted
Receives an object with:
| Field | Type | Description |
| ------------- | -------- | ------------------------------ |
| `token` | `string` | The token used for signing. |
| `documentId` | `number` | The ID of the signed document. |
| `recipientId` | `number` | The ID of the recipient. |
### onFieldSigned
Receives an object with:
| Field | Type | Description |
| ---------- | --------- | -------------------------------------------- |
| `fieldId` | `number` | The ID of the field. |
| `value` | `string` | The field value. |
| `isBase64` | `boolean` | Whether the value is a base64 encoded image. |
### onFieldUnsigned
Receives an object with:
| Field | Type | Description |
| --------- | -------- | -------------------- |
| `fieldId` | `number` | The ID of the field. |
---
## More
<Cards>
<Card
title="Direct Links"
description="Embed with iframes or redirects, no SDK required."
href="/docs/developers/embedding/direct-links"
/>
<Card
title="CSS Variables"
description="Customize colors, spacing, and theming."
href="/docs/developers/embedding/css-variables"
/>
<Card
title="Authoring"
description="Embed document and template creation."
href="/docs/developers/embedding/authoring"
/>
</Cards>
---
## See Also
- [Documents API](/docs/developers/api/documents) - Create documents programmatically
- [Templates API](/docs/developers/api/templates) - Manage templates via API
- [Webhooks](/docs/developers/webhooks) - Receive server-side signing notifications
@@ -0,0 +1,4 @@
{
"title": "Embedding",
"pages": ["sdks", "direct-links", "css-variables", "authoring"]
}
@@ -0,0 +1,92 @@
---
title: Angular
description: Embed Documenso signing in your Angular application using the official SDK.
---
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
## Installation
<Tabs items={['npm', 'yarn', 'pnpm']}>
<Tab value="npm">``` npm install @documenso/embed-angular ```</Tab>
<Tab value="yarn">``` yarn add @documenso/embed-angular ```</Tab>
<Tab value="pnpm">``` pnpm add @documenso/embed-angular ```</Tab>
</Tabs>
---
## Direct Template
```typescript
import { Component } from '@angular/core';
import { EmbedDirectTemplate } from '@documenso/embed-angular';
@Component({
selector: 'app-signing',
standalone: true,
imports: [EmbedDirectTemplate],
template: `
<embed-direct-template
[token]="token"
[name]="'John Doe'"
[email]="'john@example.com'"
[lockEmail]="true"
[externalId]="'order-12345'"
(documentReady)="onReady()"
(documentCompleted)="onCompleted($event)"
(documentError)="onError()"
/>
`,
})
export class SigningComponent {
token = 'your-template-token';
onReady() {
console.log('Ready');
}
onCompleted(data: { documentId: number }) {
console.log('Signed:', data.documentId);
}
onError() {
console.error('Error');
}
}
```
---
## Signing Token
```typescript
import { Component, Input } from '@angular/core';
import { EmbedSignDocument } from '@documenso/embed-angular';
@Component({
selector: 'app-signing',
standalone: true,
imports: [EmbedSignDocument],
template: `
<embed-sign-document
[token]="token"
(documentCompleted)="onCompleted($event)"
/>
`,
})
export class SigningComponent {
@Input() token = '';
onCompleted(data: { documentId: number }) {
console.log('Signed:', data.documentId);
}
}
```
---
## See Also
- [Embedding Overview](/docs/developers/embedding) - Props reference and concepts
- [CSS Variables](/docs/developers/embedding/css-variables) - Customize appearance
- [Authoring](/docs/developers/embedding/authoring) - Embed document creation
@@ -0,0 +1,37 @@
---
title: SDKs
description: Official embedding SDKs for React, Vue, Svelte, Angular, Solid, and Preact.
---
Install the SDK for your framework and embed document signing with a few lines of code.
<Cards>
<Card
title="React"
description="@documenso/embed-react"
href="/docs/developers/embedding/sdks/react"
/>
<Card title="Vue" description="@documenso/embed-vue" href="/docs/developers/embedding/sdks/vue" />
<Card
title="Svelte"
description="@documenso/embed-svelte"
href="/docs/developers/embedding/sdks/svelte"
/>
<Card
title="Angular"
description="@documenso/embed-angular"
href="/docs/developers/embedding/sdks/angular"
/>
<Card
title="Solid"
description="@documenso/embed-solid"
href="/docs/developers/embedding/sdks/solid"
/>
<Card
title="Preact"
description="@documenso/embed-preact"
href="/docs/developers/embedding/sdks/preact"
/>
</Cards>
If you are not using a JavaScript framework, you can embed signing using [Direct Links](/docs/developers/embedding/direct-links) with a plain iframe or redirect.
@@ -0,0 +1,4 @@
{
"title": "SDKs",
"pages": ["react", "vue", "svelte", "angular", "solid", "preact"]
}
@@ -0,0 +1,96 @@
---
title: Preact
description: Embed Documenso signing in your Preact application using the official SDK.
---
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
## Installation
<Tabs items={['npm', 'yarn', 'pnpm']}>
<Tab value="npm">``` npm install @documenso/embed-preact ```</Tab>
<Tab value="yarn">``` yarn add @documenso/embed-preact ```</Tab>
<Tab value="pnpm">``` pnpm add @documenso/embed-preact ```</Tab>
</Tabs>
---
## Direct Template
```tsx
import { EmbedDirectTemplate } from '@documenso/embed-preact';
const SigningPage = () => {
return (
<EmbedDirectTemplate
token="your-template-token"
name="John Doe"
email="john@example.com"
lockEmail={true}
externalId="order-12345"
onDocumentReady={() => console.log('Ready')}
onDocumentCompleted={(data) => {
console.log('Signed:', data.documentId);
}}
onDocumentError={() => console.error('Error')}
/>
);
};
```
---
## Signing Token
```tsx
import { EmbedSignDocument } from '@documenso/embed-preact';
const SigningPage = ({ token }: { token: string }) => {
return (
<EmbedSignDocument
token={token}
onDocumentCompleted={(data) => {
console.log('Signed:', data.documentId);
}}
/>
);
};
```
---
## Styling (Platform Plan)
```tsx
import { EmbedDirectTemplate } from '@documenso/embed-preact';
const StyledEmbed = () => {
return (
<EmbedDirectTemplate
token="your-token"
css={`
.documenso-embed {
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
`}
cssVars={{
primary: '#0000FF',
background: '#F5F5F5',
radius: '8px',
}}
darkModeDisabled={true}
/>
);
};
```
See [CSS Variables](/docs/developers/embedding/css-variables) for all available variables.
---
## See Also
- [Embedding Overview](/docs/developers/embedding) - Props reference and concepts
- [CSS Variables](/docs/developers/embedding/css-variables) - Customize appearance
- [Authoring](/docs/developers/embedding/authoring) - Embed document creation
@@ -0,0 +1,136 @@
---
title: React
description: Embed Documenso signing in your React application using the official SDK.
---
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
## Installation
<Tabs items={['npm', 'yarn', 'pnpm']}>
<Tab value="npm">``` npm install @documenso/embed-react ```</Tab>
<Tab value="yarn">``` yarn add @documenso/embed-react ```</Tab>
<Tab value="pnpm">``` pnpm add @documenso/embed-react ```</Tab>
</Tabs>
---
## Direct Template
```tsx
import { EmbedDirectTemplate } from '@documenso/embed-react';
const SigningPage = () => {
return (
<EmbedDirectTemplate
token="your-template-token"
name="John Doe"
email="john@example.com"
lockEmail={true}
externalId="order-12345"
onDocumentReady={() => console.log('Ready')}
onDocumentCompleted={(data) => {
console.log('Signed:', data.documentId);
}}
onDocumentError={() => console.error('Error')}
/>
);
};
```
---
## Signing Token
```tsx
import { EmbedSignDocument } from '@documenso/embed-react';
const SigningPage = ({ token }: { token: string }) => {
return (
<EmbedSignDocument
token={token}
onDocumentCompleted={(data) => {
console.log('Signed:', data.documentId);
}}
/>
);
};
```
---
## Styling (Platform Plan)
```tsx
import { EmbedDirectTemplate } from '@documenso/embed-react';
const StyledEmbed = () => {
return (
<EmbedDirectTemplate
token="your-token"
css={`
.documenso-embed {
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
`}
cssVars={{
primary: '#0000FF',
background: '#F5F5F5',
radius: '8px',
}}
darkModeDisabled={true}
/>
);
};
```
See [CSS Variables](/docs/developers/embedding/css-variables) for all available variables.
---
## Complete Example
```tsx
import { useState } from 'react';
import { EmbedDirectTemplate } from '@documenso/embed-react';
type Status = 'loading' | 'ready' | 'completed' | 'error';
const DocumentSigning = ({ token }: { token: string }) => {
const [status, setStatus] = useState<Status>('loading');
if (status === 'completed') {
return <p>Thank you for signing the document.</p>;
}
if (status === 'error') {
return <p>An error occurred. Please try again.</p>;
}
return (
<div style={{ position: 'relative', height: '100vh' }}>
{status === 'loading' && (
<div style={{ position: 'absolute', inset: 0, display: 'grid', placeItems: 'center' }}>
Loading...
</div>
)}
<EmbedDirectTemplate
token={token}
onDocumentReady={() => setStatus('ready')}
onDocumentCompleted={() => setStatus('completed')}
onDocumentError={() => setStatus('error')}
/>
</div>
);
};
```
---
## See Also
- [Embedding Overview](/docs/developers/embedding) - Props reference and concepts
- [CSS Variables](/docs/developers/embedding/css-variables) - Customize appearance
- [Authoring](/docs/developers/embedding/authoring) - Embed document creation
@@ -0,0 +1,96 @@
---
title: Solid
description: Embed Documenso signing in your Solid application using the official SDK.
---
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
## Installation
<Tabs items={['npm', 'yarn', 'pnpm']}>
<Tab value="npm">``` npm install @documenso/embed-solid ```</Tab>
<Tab value="yarn">``` yarn add @documenso/embed-solid ```</Tab>
<Tab value="pnpm">``` pnpm add @documenso/embed-solid ```</Tab>
</Tabs>
---
## Direct Template
```tsx
import { EmbedDirectTemplate } from '@documenso/embed-solid';
const SigningPage = () => {
return (
<EmbedDirectTemplate
token="your-template-token"
name="John Doe"
email="john@example.com"
lockEmail={true}
externalId="order-12345"
onDocumentReady={() => console.log('Ready')}
onDocumentCompleted={(data) => {
console.log('Signed:', data.documentId);
}}
onDocumentError={() => console.error('Error')}
/>
);
};
```
---
## Signing Token
```tsx
import { EmbedSignDocument } from '@documenso/embed-solid';
const SigningPage = (props: { token: string }) => {
return (
<EmbedSignDocument
token={props.token}
onDocumentCompleted={(data) => {
console.log('Signed:', data.documentId);
}}
/>
);
};
```
---
## Styling (Platform Plan)
```tsx
import { EmbedDirectTemplate } from '@documenso/embed-solid';
const StyledEmbed = () => {
return (
<EmbedDirectTemplate
token="your-token"
css={`
.documenso-embed {
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
`}
cssVars={{
primary: '#0000FF',
background: '#F5F5F5',
radius: '8px',
}}
darkModeDisabled={true}
/>
);
};
```
See [CSS Variables](/docs/developers/embedding/css-variables) for all available variables.
---
## See Also
- [Embedding Overview](/docs/developers/embedding) - Props reference and concepts
- [CSS Variables](/docs/developers/embedding/css-variables) - Customize appearance
- [Authoring](/docs/developers/embedding/authoring) - Embed document creation
@@ -0,0 +1,104 @@
---
title: Svelte
description: Embed Documenso signing in your Svelte application using the official SDK.
---
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
## Installation
<Tabs items={['npm', 'yarn', 'pnpm']}>
<Tab value="npm">``` npm install @documenso/embed-svelte ```</Tab>
<Tab value="yarn">``` yarn add @documenso/embed-svelte ```</Tab>
<Tab value="pnpm">``` pnpm add @documenso/embed-svelte ```</Tab>
</Tabs>
---
## Direct Template
```svelte
<script lang="ts">
import { EmbedDirectTemplate } from '@documenso/embed-svelte';
const token = 'your-template-token';
function onCompleted(data: { documentId: number }) {
console.log('Signed:', data.documentId);
}
</script>
<EmbedDirectTemplate
{token}
name="John Doe"
email="john@example.com"
lockEmail={true}
externalId="order-12345"
onDocumentReady={() => console.log('Ready')}
onDocumentCompleted={onCompleted}
onDocumentError={() => console.error('Error')}
/>
```
---
## Signing Token
```svelte
<script lang="ts">
import { EmbedSignDocument } from '@documenso/embed-svelte';
export let token: string;
function onCompleted(data: { documentId: number }) {
console.log('Signed:', data.documentId);
}
</script>
<EmbedSignDocument
{token}
onDocumentCompleted={onCompleted}
/>
```
---
## Styling (Platform Plan)
```svelte
<script lang="ts">
import { EmbedDirectTemplate } from '@documenso/embed-svelte';
const token = 'your-token';
const customCss = `
.documenso-embed {
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
`;
const cssVars = {
primary: '#0000FF',
background: '#F5F5F5',
radius: '8px',
};
</script>
<EmbedDirectTemplate
{token}
css={customCss}
cssVars={cssVars}
darkModeDisabled={true}
/>
```
See [CSS Variables](/docs/developers/embedding/css-variables) for all available variables.
---
## See Also
- [Embedding Overview](/docs/developers/embedding) - Props reference and concepts
- [CSS Variables](/docs/developers/embedding/css-variables) - Customize appearance
- [Authoring](/docs/developers/embedding/authoring) - Embed document creation
@@ -0,0 +1,107 @@
---
title: Vue
description: Embed Documenso signing in your Vue application using the official SDK.
---
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
## Installation
<Tabs items={['npm', 'yarn', 'pnpm']}>
<Tab value="npm">``` npm install @documenso/embed-vue ```</Tab>
<Tab value="yarn">``` yarn add @documenso/embed-vue ```</Tab>
<Tab value="pnpm">``` pnpm add @documenso/embed-vue ```</Tab>
</Tabs>
---
## Direct Template
```html
<script setup lang="ts">
import { EmbedDirectTemplate } from '@documenso/embed-vue';
const token = 'your-template-token';
function onCompleted(data: { documentId: number }) {
console.log('Signed:', data.documentId);
}
</script>
<template>
<EmbedDirectTemplate
:token="token"
name="John Doe"
email="john@example.com"
:lockEmail="true"
externalId="order-12345"
@document-completed="onCompleted"
@document-ready="() => console.log('Ready')"
@document-error="() => console.error('Error')"
/>
</template>
```
---
## Signing Token
```html
<script setup lang="ts">
import { EmbedSignDocument } from '@documenso/embed-vue';
const props = defineProps<{ token: string }>();
function onCompleted(data: { documentId: number }) {
console.log('Signed:', data.documentId);
}
</script>
<template>
<EmbedSignDocument :token="props.token" @document-completed="onCompleted" />
</template>
```
---
## Styling (Platform Plan)
```html
<script setup lang="ts">
import { EmbedDirectTemplate } from '@documenso/embed-vue';
const token = 'your-token';
const customCss = `
.documenso-embed {
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
`;
const cssVars = {
primary: '#0000FF',
background: '#F5F5F5',
radius: '8px',
};
</script>
<template>
<EmbedDirectTemplate
:token="token"
:css="customCss"
:cssVars="cssVars"
:darkModeDisabled="true"
/>
</template>
```
See [CSS Variables](/docs/developers/embedding/css-variables) for all available variables.
---
## See Also
- [Embedding Overview](/docs/developers/embedding) - Props reference and concepts
- [CSS Variables](/docs/developers/embedding/css-variables) - Customize appearance
- [Authoring](/docs/developers/embedding/authoring) - Embed document creation
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,12 @@
---
title: Examples
description: Common integration patterns and end-to-end workflows.
---
<Cards>
<Card
title="Common Workflows"
description="End-to-end examples for typical use cases."
href="/docs/developers/examples/common-workflows"
/>
</Cards>
@@ -0,0 +1,4 @@
{
"title": "Examples",
"pages": ["common-workflows"]
}
@@ -0,0 +1,202 @@
---
title: Authentication
description: Generate an API key and authenticate your requests.
---
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';
## Prerequisites
- A Documenso account (cloud or self-hosted)
- A Documenso account on any plan (Free, Individual, Team, or Enterprise)
<Callout type="info">
Free accounts include API access with a limit of 5 documents per month. [Upgrade to a paid
plan](https://documen.so/pricing) for higher limits.
</Callout>
## Create an API Token
{/* prettier-ignore */}
<Steps>
<Step>
### Open settings
- Log in to your Documenso account
- Click your avatar in the top right corner
- Select **Settings** from the dropdown menu
![User dropdown menu](/public-api-images/documenso-user-dropdown-menu.webp)
</Step>
<Step>
### Navigate to the API Tokens tab
Go to **Settings** and open the **API Tokens** tab.
![API tokens page](/public-api-images/api-tokens-page-documenso.webp)
</Step>
<Step>
### Generate a new token
- Click **Create Token**
- Enter a descriptive name (e.g., `production-backend`, `zapier-integration`)
- Select an expiration period: never expires, 7 days, 1 month, 3 months, 6 months, or 1 year
- Click **Create Token**
</Step>
<Step>
### Copy your token
Your token is displayed once after creation. Copy it immediately and store it securely.
![API key display](/public-api-images/documenso-api-key-blurred.webp)
<Callout type="warn">
You cannot view the token again after leaving this page. If you lose it, you must create a new
token.
</Callout>
</Step>
</Steps>
## Using Your Token
Include the token in the `Authorization` header of your HTTP requests.
### cURL
```bash
curl https://app.documenso.com/api/v2/documents \
-H "Authorization: api_xxxxxxxxxxxxxxxx"
```
### JavaScript / TypeScript
```typescript
const response = await fetch('https://app.documenso.com/api/v2/documents', {
method: 'GET',
headers: {
Authorization: 'api_xxxxxxxxxxxxxxxx',
},
});
const documents = await response.json();
```
### Using the TypeScript SDK
Documenso provides official SDKs that handle authentication for you:
```typescript
import { Documenso } from '@documenso/sdk-typescript';
const client = new Documenso({
apiKey: 'api_xxxxxxxxxxxxxxxx',
});
const documents = await client.documents.find();
```
SDKs are available for [TypeScript](https://github.com/documenso/sdk-typescript), [Python](https://github.com/documenso/sdk-python), and [Go](https://github.com/documenso/sdk-go).
## API Base URLs
| Environment | Base URL |
| ----------- | -------------------------------------- |
| Production | `https://app.documenso.com/api/v2` |
| Staging | `https://stg-app.documenso.com/api/v2` |
| Self-hosted | `https://your-domain.com/api/v2` |
<Callout type="info">
API V1 is deprecated. Use V2 for all new integrations. V1 only works with legacy documents created
before the envelope system. If you need V1 documentation for migration purposes, see the [V1
OpenAPI reference](https://app.documenso.com/api/v1/openapi).
</Callout>
<Callout type="info">
The API is available on all plans, including Free (5 documents per month). [Fair
Use](/docs/policies/fair-use) applies to all API usage.
</Callout>
## Token Security
API tokens grant full access to your account. Follow these practices to keep them secure:
- **Never commit tokens to version control.** Use environment variables instead.
- **Use descriptive names.** Names like `zapier-prod` or `backend-staging` help you identify token usage.
- **Set expiration dates.** Shorter expiration periods reduce risk if a token is compromised.
- **Rotate tokens regularly.** Create new tokens and revoke old ones periodically.
- **Use separate tokens per integration.** If one is compromised, you only need to revoke that specific token.
- **Revoke unused tokens.** Delete tokens you no longer need from the API Tokens settings page.
### Environment Variables
Store your token in an environment variable rather than hardcoding it:
```bash
# .env (do not commit this file)
DOCUMENSO_API_KEY=api_xxxxxxxxxxxxxxxx
```
```typescript
const client = new Documenso({
apiKey: process.env.DOCUMENSO_API_KEY,
});
```
## Token Scope
API tokens have full access to your account, including:
- Creating, reading, updating, and deleting documents
- Managing recipients and fields
- Accessing templates
- Managing team resources (if the token owner has team access)
There is currently no way to create tokens with limited scopes or permissions.
## Revoking a Token
To revoke a token:
{/* prettier-ignore */}
<Steps>
<Step>
Go to **Settings** > **API Tokens**
</Step>
<Step>
Find the token you want to revoke
</Step>
<Step>
Click the delete icon next to the token
</Step>
<Step>
Confirm the deletion
</Step>
</Steps>
Revoked tokens stop working immediately. Any integrations using that token will receive `401 Unauthorized` errors.
## Troubleshooting
<Accordions type="multiple">
<Accordion title="401 Unauthorized — Missing or invalid token">
Check that you included the token in the `Authorization` header.
</Accordion>
<Accordion title="401 Unauthorized — Expired token">Create a new token in settings.</Accordion>
<Accordion title="403 Forbidden — Token doesn't have access to the resource">
Ensure you're accessing resources owned by the token's account.
</Accordion>
</Accordions>
## Next Steps
- [Make your first API call](/docs/developers/getting-started/first-api-call) - Create a document via the API
- [API Reference](/docs/developers/api) - Explore available endpoints
@@ -0,0 +1,519 @@
---
title: First API Call
description: Create and send a document for signing using the Documenso API, from uploading a PDF to adding recipients and distributing for signature.
---
import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
## Prerequisites
Before starting, you need:
- A Documenso account (cloud or self-hosted)
- An API token ([create one in team settings](/docs/developers/getting-started/authentication))
- A PDF file to send for signing
<Callout type="warn">
API tokens have full access to your account. Store them securely and never commit them to version
control.
</Callout>
## Limitations
The API cannot:
- Sign documents on behalf of recipients (recipients must sign themselves)
- Convert non-PDF files to PDF (you must upload PDFs)
- Retrieve the signed PDF until all recipients have completed signing
## Base URL
All API requests use the following base URLs:
| Environment | Base URL |
| ----------- | -------------------------------------- |
| Production | `https://app.documenso.com/api/v2` |
| Staging | `https://stg-app.documenso.com/api/v2` |
## Example 1: List Your Documents
Start with a simple GET request to verify your API token works.
<Tabs items={['curl', 'JavaScript']}>
<Tab value="curl">
```bash
curl -X GET "https://app.documenso.com/api/v2/envelope" \
-H "Authorization: YOUR_API_TOKEN"
```
</Tab>
<Tab value="JavaScript">
```javascript
const response = await fetch('https://app.documenso.com/api/v2/envelope', {
method: 'GET',
headers: {
'Authorization': 'YOUR_API_TOKEN',
},
});
const data = await response.json();
console.log(data);
````
</Tab>
</Tabs>
A successful response returns a list of your documents (envelopes):
```json
{
"data": [
{
"id": "envelope_abc123",
"status": "DRAFT",
"title": "Contract Agreement",
"createdAt": "2025-01-15T10:30:00.000Z"
}
],
"pagination": {
"page": 1,
"perPage": 10,
"totalPages": 1,
"totalItems": 1
}
}
````
If you receive a `401 Unauthorized` error, verify your API token is correct and includes the `api_` prefix.
## Example 2: Create a Document with Recipient and Signature Field
The V2 API uses a single endpoint to create a document with recipients and fields in one request. This is the most common pattern for sending documents.
<Tabs items={['curl', 'JavaScript']}>
<Tab value="curl">
```bash
curl -X POST "https://app.documenso.com/api/v2/envelope/create" \
-H "Authorization: YOUR_API_TOKEN" \
-H "Content-Type: multipart/form-data" \
-F 'payload={
"type": "DOCUMENT",
"title": "Service Agreement",
"recipients": [
{
"email": "signer@example.com",
"name": "John Smith",
"role": "SIGNER",
"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
}
]
}
]
}' \
-F "files=@./contract.pdf;type=application/pdf"
```
</Tab>
<Tab value="JavaScript">
```javascript
const fs = require('fs');
const FormData = require('form-data');
const form = new FormData();
const payload = {
type: 'DOCUMENT',
title: 'Service Agreement',
recipients: [
{
email: 'signer@example.com',
name: 'John Smith',
role: 'SIGNER',
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,
},
],
},
],
};
form.append('payload', JSON.stringify(payload));
form.append('files', fs.createReadStream('./contract.pdf'), {
contentType: 'application/pdf',
});
const response = await fetch('https://app.documenso.com/api/v2/envelope/create', {
method: 'POST',
headers: {
Authorization: 'YOUR_API_TOKEN',
},
body: form,
});
const data = await response.json();
console.log('Created envelope:', data.id);
````
</Tab>
</Tabs>
### Understanding Field Positioning
Field positions use percentage values (0-100) relative to the PDF page dimensions:
| Parameter | Description |
| --------- | ----------- |
| `positionX` | Horizontal position from left edge (0 = left, 100 = right) |
| `positionY` | Vertical position from top edge (0 = top, 100 = bottom) |
| `width` | Field width as percentage of page width |
| `height` | Field height as percentage of page height |
| `page` | Page number (1-indexed) |
| `identifier` | Index of the file (0 for first file, 1 for second, etc.) |
<Callout type="info">
To place a signature near the bottom-left of the page, use `positionX: 10` and `positionY: 80`.
</Callout>
### Recipient Roles
| Role | Description |
| ---- | ----------- |
| `SIGNER` | Must sign the document |
| `APPROVER` | Must approve before signers can sign |
| `CC` | Receives a copy but doesn't sign |
| `VIEWER` | Can view the document but takes no action |
<Callout type="info">
See the [recipient roles](/docs/concepts/recipient-roles) page for more information.
</Callout>
## Example 3: Send the Document for Signing
After creating a document, it's in `DRAFT` status. To send it to recipients, use the distribute endpoint:
<Tabs items={['curl', 'JavaScript']}>
<Tab value="curl">
```bash
curl -X POST "https://app.documenso.com/api/v2/envelope/envelope_abc123/distribute" \
-H "Authorization: YOUR_API_TOKEN" \
-H "Content-Type: application/json"
````
</Tab>
<Tab value="JavaScript">
```javascript
const envelopeId = 'envelope_abc123';
const response = await fetch(
`https://app.documenso.com/api/v2/envelope/${envelopeId}/distribute`,
{
method: 'POST',
headers: {
Authorization: 'YOUR_API_TOKEN',
'Content-Type': 'application/json',
},
},
);
const data = await response.json();
console.log('Document sent:', data);
````
</Tab>
</Tabs>
After distribution, recipients receive an email with a link to sign the document. The document status changes from `DRAFT` to `PENDING`.
## Full Workflow Example
Here's a complete script that creates and sends a document:
<Tabs items={['JavaScript', 'curl']}>
<Tab value="JavaScript">
```javascript
const fs = require('fs');
const FormData = require('form-data');
const API_TOKEN = process.env.DOCUMENSO_API_TOKEN;
const BASE_URL = 'https://app.documenso.com/api/v2';
async function createAndSendDocument(pdfPath, recipientEmail, recipientName) {
// Step 1: Create the envelope with recipient and fields
const form = new FormData();
const payload = {
type: 'DOCUMENT',
title: 'Service Agreement',
recipients: [
{
email: recipientEmail,
name: recipientName,
role: 'SIGNER',
fields: [
{
identifier: 0,
type: 'SIGNATURE',
page: 1,
positionX: 10,
positionY: 80,
width: 30,
height: 5,
},
{
identifier: 0,
type: 'NAME',
page: 1,
positionX: 10,
positionY: 75,
width: 30,
height: 3,
},
{
identifier: 0,
type: 'DATE',
page: 1,
positionX: 50,
positionY: 80,
width: 20,
height: 3,
},
],
},
],
};
form.append('payload', JSON.stringify(payload));
form.append('files', fs.createReadStream(pdfPath), {
contentType: 'application/pdf',
});
const createResponse = await fetch(`${BASE_URL}/envelope/create`, {
method: 'POST',
headers: {
'Authorization': API_TOKEN,
},
body: form,
});
if (!createResponse.ok) {
const error = await createResponse.json();
throw new Error(`Failed to create envelope: ${JSON.stringify(error)}`);
}
const envelope = await createResponse.json();
console.log('Created envelope:', envelope.id);
// Step 2: Send the document for signing
const distributeResponse = await fetch(
`${BASE_URL}/envelope/${envelope.id}/distribute`,
{
method: 'POST',
headers: {
'Authorization': API_TOKEN,
'Content-Type': 'application/json',
},
}
);
if (!distributeResponse.ok) {
const error = await distributeResponse.json();
throw new Error(`Failed to distribute envelope: ${JSON.stringify(error)}`);
}
console.log('Document sent for signing!');
return envelope.id;
}
// Usage
createAndSendDocument(
'./contract.pdf',
'signer@example.com',
'John Smith'
).catch(console.error);
````
</Tab>
<Tab value="curl">
```bash
#!/bin/bash
set -e
API_TOKEN="YOUR_API_TOKEN"
BASE_URL="https://app.documenso.com/api/v2"
PDF_FILE="./contract.pdf"
RECIPIENT_EMAIL="signer@example.com"
RECIPIENT_NAME="John Smith"
# Step 1: Create the envelope with recipient and fields
echo "Creating envelope..."
ENVELOPE_RESPONSE=$(curl -s -X POST "${BASE_URL}/envelope/create" \
-H "Authorization: ${API_TOKEN}" \
-H "Content-Type: multipart/form-data" \
-F "payload={
\"type\": \"DOCUMENT\",
\"title\": \"Service Agreement\",
\"recipients\": [
{
\"email\": \"${RECIPIENT_EMAIL}\",
\"name\": \"${RECIPIENT_NAME}\",
\"role\": \"SIGNER\",
\"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
}
]
}
]
}" \
-F "files=@${PDF_FILE};type=application/pdf")
ENVELOPE_ID=$(echo $ENVELOPE_RESPONSE | jq -r '.id')
echo "Created envelope: ${ENVELOPE_ID}"
# Step 2: Send the document for signing
echo "Sending document..."
curl -s -X POST "${BASE_URL}/envelope/${ENVELOPE_ID}/distribute" \
-H "Authorization: ${API_TOKEN}" \
-H "Content-Type: application/json"
echo "Document sent for signing!"
````
</Tab>
</Tabs>
## Error Handling
The API returns standard HTTP status codes and JSON error responses:
| Status Code | Meaning |
| ----------- | ------- |
| `400` | Bad request - check your request payload |
| `401` | Unauthorized - invalid or missing API token |
| `404` | Not found - resource doesn't exist |
| `429` | Rate limited - wait 60 seconds and retry |
| `500` | Server error - retry or contact support |
### Error Response Format
```json
{
"error": "Description of what went wrong",
"code": "ERROR_CODE",
"statusCode": 400
}
````
### Common Errors
**Invalid file type:**
```json
{
"error": "Invalid file type. Only PDF files are supported.",
"statusCode": 400
}
```
**Missing required field:**
```json
{
"error": "Recipient email is required",
"statusCode": 400
}
```
**Envelope not found:**
```json
{
"error": "Envelope not found",
"statusCode": 404
}
```
### Handling Rate Limits
The API allows 100 requests per minute per IP address. When rate limited, wait at least 60 seconds before retrying:
```javascript
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status === 429) {
console.log('Rate limited, waiting 60 seconds...');
await new Promise((resolve) => setTimeout(resolve, 60000));
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}
```
## Next Steps
- [API Reference](https://openapi.documenso.com/) - Full endpoint documentation with request/response schemas
- [Webhooks](/docs/developers/webhooks/setup) - Get notified when documents are signed
- [Templates](/docs/developers/api/templates) - Create reusable document templates
- [SDKs](#official-sdks) - Use typed client libraries
### Official SDKs
For production applications, consider using the official SDKs:
- [TypeScript SDK](https://github.com/documenso/sdk-typescript)
- [Python SDK](https://github.com/documenso/sdk-python)
- [Go SDK](https://github.com/documenso/sdk-go)
@@ -0,0 +1,17 @@
---
title: Getting Started
description: Get your API key and make your first API call.
---
<Cards>
<Card
title="Authentication"
description="Generate an API key and authenticate your requests."
href="/docs/developers/getting-started/authentication"
/>
<Card
title="First API Call"
description="Create your first document via the API."
href="/docs/developers/getting-started/first-api-call"
/>
</Cards>
@@ -0,0 +1,4 @@
{
"title": "Getting Started",
"pages": ["authentication", "first-api-call"]
}
@@ -0,0 +1,87 @@
---
title: Developer Guide
description: Integrate Documenso into your applications using the REST API, webhooks, and embedding options.
---
## Getting Started
<Cards>
<Card
title="Authentication"
description="Get your API key and authenticate requests."
href="/docs/developers/getting-started/authentication"
/>
<Card
title="First API Call"
description="Create and send your first document via the API."
href="/docs/developers/getting-started/first-api-call"
/>
</Cards>
---
## Integration Options
<Cards>
<Card
title="API Reference"
description="Documents, recipients, fields, templates, and teams."
href="/docs/developers/api"
/>
<Card
title="Webhooks"
description="Receive notifications when documents are signed or updated."
href="/docs/developers/webhooks"
/>
<Card
title="Embedding"
description="Embed signing experiences using direct links or React."
href="/docs/developers/embedding"
/>
<Card
title="Examples"
description="Common integration patterns and workflows."
href="/docs/developers/examples"
/>
</Cards>
---
## API Base URL
```
https://app.documenso.com/api/v2
```
For self-hosted instances, replace with your instance URL:
```
https://your-instance.com/api/v2
```
---
## SDKs
Official SDKs are available for multiple languages:
- [TypeScript SDK](https://github.com/documenso/sdk-typescript)
- [Python SDK](https://github.com/documenso/sdk-python)
- [Go SDK](https://github.com/documenso/sdk-go)
---
## Looking for Something Else?
<Cards>
<Card
title="User Guide"
description="Send documents using the web application."
href="/docs/users"
/>
<Card
title="Self-Hosting"
description="Deploy your own Documenso instance."
href="/docs/self-hosting"
/>
</Cards>
@@ -0,0 +1,13 @@
---
title: Run in Gitpod
description: Get started with Documenso in a ready-to-use Gitpod workspace in your browser.
---
Click below to launch a ready-to-use Gitpod workspace in your browser.
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/documenso/documenso)
## See Also
- [Developer Quickstart](/docs/developers/local-development/quickstart) - Local Docker-based setup
- [Manual Setup](/docs/developers/local-development/manual) - Manual setup without Docker
@@ -0,0 +1,66 @@
---
title: Local Development
description: Learn how to set up Documenso for local development.
---
## Overview
There are multiple ways of setting up Documenso for local development. At the moment of writing this documentation, there are 3 ways of running Documenso locally:
- [Using the developer quickstart with Docker](/docs/developers/local-development/quickstart)
- [Manually setting up the development environment](/docs/developers/local-development/manual)
- [Using Gitpod](/docs/developers/local-development/gitpod)
Pick the one that fits your needs the best.
## Tech Stack
- [Typescript](https://www.typescriptlang.org/) - Language
- [React Router](https://reactrouter.com/) - Framework
- [Prisma](https://www.prisma.io/) - ORM
- [Tailwind](https://tailwindcss.com/) - CSS
- [shadcn/ui](https://ui.shadcn.com/) - Component Library
- [react-email](https://react.email/) - Email Templates
- [tRPC](https://trpc.io/) - API
- [@documenso/pdf-sign](https://www.npmjs.com/package/@documenso/pdf-sign) - PDF Signatures
- [React-PDF](https://github.com/wojtekmaj/react-pdf) - Viewing PDFs
- [PDF-Lib](https://github.com/Hopding/pdf-lib) - PDF manipulation
- [Stripe](https://stripe.com/) - Payments
<div className="mt-16 flex items-center justify-center gap-4">
<a href="https://documen.so/discord">
<img
src="https://img.shields.io/badge/Discord-documen.so/discord-%235865F2"
alt="Join Documenso on Discord"
/>
</a>
<a href="https://github.com/documenso/documenso/stargazers">
<img src="https://img.shields.io/github/stars/documenso/documenso" alt="Github Stars" />
</a>
<a href="https://github.com/documenso/documenso/blob/main/LICENSE">
<img src="https://img.shields.io/badge/license-AGPLv3-purple" alt="License" />
</a>
<a href="https://github.com/documenso/documenso/pulse">
<img
src="https://img.shields.io/github/commit-activity/m/documenso/documenso"
alt="Commits-per-month"
/>
</a>
<a href="https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/documenso/documenso">
<img
alt="open in devcontainer"
src="https://img.shields.io/static/v1?label=Dev%20Containers&message=Enabled&color=blue&logo=visualstudiocode"
/>
</a>
<a href="https://github.com/documenso/documenso/blob/main/CODE_OF_CONDUCT.md">
<img
src="https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg"
alt="Contributor Covenant"
/>
</a>
</div>
## See Also
- [Contributing](/docs/developers/contributing) - Learn how to contribute to Documenso
- [Self-Hosting](/docs/self-hosting) - Deploy your own instance
@@ -0,0 +1,111 @@
---
title: Manual Setup
description: Manually set up Documenso on your machine for local development.
---
import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';
# Manual Setup
Follow these steps to set up Documenso on your local machine:
{/* prettier-ignore */}
<Steps>
<Step>
### Fork Documenso
Fork the [Documenso repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks) to your GitHub account.
</Step>
<Step>
### Clone repository
After forking the repository, clone it to your local device by using the following command:
```bash
git clone https://github.com/<your-username>/documenso
```
</Step>
<Step>
### Install dependencies
Run `npm i` in the root directory to install the dependencies required for the project.
</Step>
<Step>
### Set up environment variables
Set up the following environment variables in the `.env` file:
```bash
NEXTAUTH_SECRET
NEXT_PUBLIC_WEBAPP_URL
NEXT_PRIVATE_DATABASE_URL
NEXT_PRIVATE_DIRECT_DATABASE_URL
NEXT_PRIVATE_SMTP_FROM_NAME
NEXT_PRIVATE_SMTP_FROM_ADDRESS
```
Alternatively, you can run `cp .env.example .env` to get started with our handpicked defaults.
<Callout type="info">
See the [Environment Variables](/docs/self-hosting/configuration/environment) page for more
information.
</Callout>
</Step>
<Step>
### Create database schema
Create the database schema by running the following command:
```bash
npm run prisma:migrate-dev
```
</Step>
<Step>
### Optional: seed the database
Seed the database with test data by running the following command:
```bash
npm run prisma:seed -w @documenso/prisma
```
</Step>
<Step>
### Start the application
Run `npm run dev` in the root directory to start the application.
</Step>
<Step>
### Access the application
Access the Documenso application by visiting `http://localhost:3000` in your web browser.
</Step>
</Steps>
<Callout type="info">
Optional: Create your signing certificate. To generate your own using these steps and a Linux
Terminal or Windows Subsystem for Linux (WSL), see **[Create your signing
certificate](/docs/developers/local-development/signing-certificate)**.
</Callout>
## See Also
- [Developer Quickstart](/docs/developers/local-development/quickstart) - Quick Docker-based setup
- [Signing Certificate](/docs/developers/local-development/signing-certificate) - Create a signing certificate
@@ -0,0 +1,4 @@
{
"title": "Local Development",
"pages": ["quickstart", "manual", "signing-certificate", "translations", "gitpod"]
}
@@ -0,0 +1,91 @@
---
title: Developer Quickstart
description: Quickly set up Documenso on your machine for local development with Docker and Docker Compose.
---
import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';
## Quickstart
<Callout type="info">
**Note**: This guide assumes that you have both [docker](https://docs.docker.com/get-docker/) and
[docker-compose](https://docs.docker.com/compose/) installed on your machine.
</Callout>
Want to get up and running quickly? Follow these steps:
{/* prettier-ignore */}
<Steps>
<Step>
### Fork Documenso
Fork the [Documenso repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks) to your GitHub account.
</Step>
<Step>
### Clone repository
After forking the repository, clone it to your local device by using the following command:
```bash
git clone https://github.com/<your-username>/documenso
```
</Step>
<Step>
### Set up environment variables
Set up your environment variables in the `.env` file using the `.env.example` file as a reference.
Alternatively, you can run `cp .env.example .env` to get started with our handpicked defaults.
</Step>
<Step>
### Start database and mail server
Run `npm run dx` in the root directory.
This will spin up a Postgres database and inbucket mailserver in a docker container.
</Step>
<Step>
### Start the application
Run `npm run dev` in the root directory to start the application.
</Step>
<Step>
### (Optional) Fasten the Process
Want it even faster? Just use:
```sh
npm run d
```
</Step>
</Steps>
### Access Points for the Project
You can access the following services:
- Main application - http://localhost:3000
- Incoming Mail Access - http://localhost:9000
- Database Connection Details:
- Port: 54320
- Connection: Use your favorite database client to connect to the database.
- S3 Storage Dashboard - http://localhost:9001
## See Also
- [Manual Setup](/docs/developers/local-development/manual) - Set up without Docker
- [Signing Certificate](/docs/developers/local-development/signing-certificate) - Create a certificate for local development
@@ -0,0 +1,92 @@
---
title: Signing Certificate
description: Learn how to create a free, self-signed certificate for local development.
---
import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';
# Create Your Signing Certificate
Digitally signing documents requires a signing certificate in `.p12` format. You can either purchase one or create a free self-signed certificate.
Follow the steps below to create a free, self-signed certificate for local development.
<Callout type="warn">
These steps should be run on a UNIX based system, otherwise you may run into an error.
</Callout>
{/* prettier-ignore */}
<Steps>
<Step>
### Generate private key
Generate a private key using OpenSSL by running the following command:
```bash
openssl genrsa -out private.key 2048
```
This command generates a 2048-bit RSA key.
</Step>
<Step>
### Generate self-signed certificate
Using the private key, generate a self-signed certificate by running the following command:
```bash
openssl req -new -x509 -key private.key -out certificate.crt -days 365
```
You will be prompted to enter some information, such as the certificate's Common Name (CN). Ensure that you provide the correct details. The `—days` parameter specifies the certificate's validity period.
</Step>
<Step>
### Create `p12` certificate
Combine the private key and the self-signed certificate to create a `.p12` certificate. Use the following command:
```bash
openssl pkcs12 -export -out certificate.p12 -inkey private.key -in certificate.crt -legacy
```
<Callout type="warn">
When running the application in Docker, you may encounter permission issues when attempting to sign documents using your certificate (.p12) file. This happens because the application runs as a non-root user inside the container and needs read access to the certificate.
To resolve this, you'll need to update the certificate file permissions to allow the container user 1001, which runs NextJS, to read it:
```bash
sudo chown 1001 certificate.p12
```
</Callout>
</Step>
<Step>
### `p12` certificate password
When you create the `.p12` certificate, you will be prompted to enter a password. Enter a strong password and keep it secure. Remember this password, as it will be required when using the certificate.
Note that for local development, the password can be left empty.
</Step>
<Step>
### Add certificate to the project
Use the `NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH` environment variable to point at the certificate you created.
Details about environment variables associated with certificates can be found [here](/docs/self-hosting/configuration/signing-certificate).
</Step>
</Steps>
## See Also
- [Signing Certificates (Self-Hosting)](/docs/self-hosting/configuration/signing-certificate) - Production certificate configuration
- [Signing Certificates (Concepts)](/docs/concepts/signing-certificates) - How digital signing works
@@ -0,0 +1,93 @@
---
title: Translations
description: Handling translations in code.
---
## Overview
Documenso uses the following stack to handle translations:
- [Lingui](https://lingui.dev/) - React i10n library
- [Crowdin](https://crowdin.com/) - Handles syncing translations
- [OpenAI](https://openai.com/) - Provides AI translations
Additional reading can be found in the [Lingui documentation](https://lingui.dev/introduction).
## Quick Guide
If you require more in-depth information, please see the [Lingui documentation](https://lingui.dev/introduction).
### HTML
Wrap all text to translate in **`<Trans></Trans>`** tags exported from **@lingui/react/macro**.
```html
<h1>
<Trans>Title</Trans>
</h1>
```
For text that is broken into elements, but represent a whole sentence, you must wrap it in a Trans tag so ensure the full message is extracted correctly.
```html
<h1>
<Trans>
This is one
<span className="text-foreground/60">full</span>
<a href="https://documenso.com">sentence</a>
</Trans>
</h1>
```
### Constants outside of react components
```tsx
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
// Wrap text in msg`text to translate` when it's in a constant here, or another file/package.
export const CONSTANT_WITH_MSG = {
foo: msg`Hello`,
bar: msg`World`,
};
export const SomeComponent = () => {
const { _ } = useLingui();
return (
<div>
{/* This will render the correct translated text. */}
<p>{_(CONSTANT_WITH_MSG.foo)}</p>
</div>
);
};
```
### Plurals
Lingui provides a Plural component to make it easy. See full documentation [here.](https://lingui.dev/ref/macro#plural-1)
```tsx
// Basic usage.
<Plural one="1 Recipient" other="# Recipients" value={recipients.length} />
```
### Dates
Lingui provides a [DateTime instance](https://lingui.dev/ref/core#i18n.date) with the configured locale.
```tsx
import { Trans } from '@lingui/macro';
import { useLingui } from '@lingui/react';
export const SomeComponent = () => {
const { i18n } = useLingui();
return <Trans>The current date is {i18n.date(new Date(), { dateStyle: 'short' })}</Trans>;
};
```
## See Also
- [Contributing Translations](/docs/developers/contributing/contributing-translations) - Help translate Documenso
@@ -0,0 +1,15 @@
{
"title": "Developers",
"description": "Integrate with Documenso",
"root": true,
"pages": [
"getting-started",
"api",
"webhooks",
"embedding",
"examples",
"local-development",
"contributing",
"demo-environment"
]
}
@@ -0,0 +1,679 @@
---
title: Webhook Events
description: Reference for all webhook event types and payloads.
---
import { Step, Steps } from 'fumadocs-ui/components/steps';
## Event Payload Structure
All webhook events share a common structure:
```json
{
"event": "DOCUMENT_COMPLETED",
"payload": {
// Document data with recipients
},
"createdAt": "2024-04-22T11:52:18.277Z",
"webhookEndpoint": "https://your-endpoint.com/webhook"
}
```
### Top-Level Fields
| Field | Type | Description |
| ----------------- | -------- | ------------------------------------------------ |
| `event` | string | Event type identifier (e.g., `DOCUMENT_CREATED`) |
| `payload` | object | Document object with metadata and recipients |
| `createdAt` | datetime | When the webhook event was created |
| `webhookEndpoint` | string | The URL receiving this webhook |
### Payload Fields
| Field | Type | Description |
| ---------------- | --------- | ------------------------------------------------------ |
| `id` | number | Document ID |
| `externalId` | string? | External identifier for integration |
| `userId` | number | Owner's user ID |
| `authOptions` | object? | Document-level authentication options |
| `formValues` | object? | PDF form values associated with the document |
| `title` | string | Document title |
| `status` | string | Current status: `DRAFT`, `PENDING`, `COMPLETED` |
| `documentDataId` | string | Reference to the document's PDF data |
| `visibility` | string | Document visibility setting |
| `createdAt` | datetime | Document creation timestamp |
| `updatedAt` | datetime | Last modification timestamp |
| `completedAt` | datetime? | Completion timestamp (when all recipients have signed) |
| `deletedAt` | datetime? | Deletion timestamp |
| `teamId` | number? | Team ID if document belongs to a team |
| `templateId` | number? | Template ID if created from a template |
| `source` | string | Source: `DOCUMENT` or `TEMPLATE` |
| `documentMeta` | object | Document metadata (subject, message, signing options) |
| `Recipient` | array | List of recipient objects |
### Document Metadata Fields
| Field | Type | Description |
| ----------------------- | ------- | --------------------------------------- |
| `id` | string | Metadata record identifier |
| `subject` | string? | Email subject line |
| `message` | string? | Email message body |
| `timezone` | string | Timezone for date display |
| `password` | string? | Document access password (if set) |
| `dateFormat` | string | Date format string |
| `redirectUrl` | string? | URL to redirect after signing |
| `signingOrder` | string | `PARALLEL` or `SEQUENTIAL` |
| `typedSignatureEnabled` | boolean | Whether typed signatures are allowed |
| `language` | string | Document language code |
| `distributionMethod` | string | How document is distributed |
| `emailSettings` | object? | Custom email settings for this document |
### Recipient Fields
| Field | Type | Description |
| ------------------- | --------- | ------------------------------------------ |
| `id` | number | Recipient ID |
| `documentId` | number | Parent document ID |
| `templateId` | number? | Template ID if created from a template |
| `email` | string | Recipient email address |
| `name` | string | Recipient name |
| `token` | string | Unique signing token |
| `documentDeletedAt` | datetime? | When the document was deleted (if deleted) |
| `expired` | boolean? | Whether the recipient's link has expired |
| `signedAt` | datetime? | When recipient signed |
| `authOptions` | object? | Per-recipient authentication options |
| `role` | string | Role: `SIGNER`, `VIEWER`, `APPROVER`, `CC` |
| `signingOrder` | number? | Position in signing sequence |
| `readStatus` | string | `NOT_OPENED` or `OPENED` |
| `signingStatus` | string | `NOT_SIGNED`, `SIGNED`, or `REJECTED` |
| `sendStatus` | string | `NOT_SENT` or `SENT` |
| `rejectionReason` | string? | Reason if recipient rejected |
---
## Document Lifecycle Events
These events track the document through its lifecycle.
### `document.created`
Triggered when a new document is uploaded.
**Event name:** `DOCUMENT_CREATED`
```json
{
"event": "DOCUMENT_CREATED",
"payload": {
"id": 10,
"externalId": null,
"userId": 1,
"authOptions": null,
"formValues": null,
"visibility": "EVERYONE",
"title": "contract.pdf",
"status": "DRAFT",
"documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0",
"createdAt": "2024-04-22T11:44:43.341Z",
"updatedAt": "2024-04-22T11:44:43.341Z",
"completedAt": null,
"deletedAt": null,
"teamId": null,
"templateId": null,
"source": "DOCUMENT",
"documentMeta": {
"id": "doc_meta_123",
"subject": "Please sign this document",
"message": "Hello, please review and sign this document.",
"timezone": "UTC",
"password": null,
"dateFormat": "MM/DD/YYYY",
"redirectUrl": null,
"signingOrder": "PARALLEL",
"typedSignatureEnabled": true,
"language": "en",
"distributionMethod": "EMAIL",
"emailSettings": null
},
"Recipient": [
{
"id": 52,
"documentId": 10,
"templateId": null,
"email": "signer@example.com",
"name": "John Doe",
"token": "vbT8hi3jKQmrFP_LN1WcS",
"documentDeletedAt": null,
"expired": null,
"signedAt": null,
"authOptions": null,
"signingOrder": 1,
"rejectionReason": null,
"role": "SIGNER",
"readStatus": "NOT_OPENED",
"signingStatus": "NOT_SIGNED",
"sendStatus": "NOT_SENT"
}
]
},
"createdAt": "2024-04-22T11:44:44.779Z",
"webhookEndpoint": "https://your-endpoint.com/webhook"
}
```
### `document.sent`
Triggered when a document is sent to recipients for signing.
**Event name:** `DOCUMENT_SENT`
The document status changes to `PENDING` and recipients have `sendStatus: "SENT"`.
```json
{
"event": "DOCUMENT_SENT",
"payload": {
"id": 10,
"externalId": null,
"userId": 1,
"authOptions": null,
"formValues": null,
"visibility": "EVERYONE",
"title": "contract.pdf",
"status": "PENDING",
"documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0",
"createdAt": "2024-04-22T11:44:43.341Z",
"updatedAt": "2024-04-22T11:48:07.569Z",
"completedAt": null,
"deletedAt": null,
"teamId": null,
"templateId": null,
"source": "DOCUMENT",
"documentMeta": {
"id": "doc_meta_123",
"subject": "Please sign this document",
"message": "Hello, please review and sign this document.",
"timezone": "UTC",
"password": null,
"dateFormat": "MM/DD/YYYY",
"redirectUrl": null,
"signingOrder": "PARALLEL",
"typedSignatureEnabled": true,
"language": "en",
"distributionMethod": "EMAIL",
"emailSettings": null
},
"Recipient": [
{
"id": 52,
"documentId": 10,
"templateId": null,
"email": "signer@example.com",
"name": "John Doe",
"token": "vbT8hi3jKQmrFP_LN1WcS",
"documentDeletedAt": null,
"expired": null,
"signedAt": null,
"authOptions": null,
"signingOrder": 1,
"rejectionReason": null,
"role": "SIGNER",
"readStatus": "NOT_OPENED",
"signingStatus": "NOT_SIGNED",
"sendStatus": "SENT"
}
]
},
"createdAt": "2024-04-22T11:48:07.945Z",
"webhookEndpoint": "https://your-endpoint.com/webhook"
}
```
### `document.completed`
Triggered when all recipients have completed their required actions.
**Event name:** `DOCUMENT_COMPLETED`
The document status changes to `COMPLETED` and `completedAt` is set.
```json
{
"event": "DOCUMENT_COMPLETED",
"payload": {
"id": 10,
"externalId": null,
"userId": 1,
"authOptions": null,
"formValues": null,
"visibility": "EVERYONE",
"title": "contract.pdf",
"status": "COMPLETED",
"documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0",
"createdAt": "2024-04-22T11:44:43.341Z",
"updatedAt": "2024-04-22T11:52:05.708Z",
"completedAt": "2024-04-22T11:52:05.707Z",
"deletedAt": null,
"teamId": null,
"templateId": null,
"source": "DOCUMENT",
"documentMeta": {
"id": "doc_meta_123",
"subject": "Please sign this document",
"message": "Hello, please review and sign this document.",
"timezone": "UTC",
"password": null,
"dateFormat": "MM/DD/YYYY",
"redirectUrl": null,
"signingOrder": "PARALLEL",
"typedSignatureEnabled": true,
"language": "en",
"distributionMethod": "EMAIL",
"emailSettings": null
},
"Recipient": [
{
"id": 50,
"documentId": 10,
"templateId": null,
"email": "reviewer@example.com",
"name": "Jane Smith",
"token": "vbT8hi3jKQmrFP_LN1WcS",
"documentDeletedAt": null,
"expired": null,
"signedAt": "2024-04-22T11:51:10.055Z",
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"signingOrder": 1,
"rejectionReason": null,
"role": "VIEWER",
"readStatus": "OPENED",
"signingStatus": "SIGNED",
"sendStatus": "SENT"
},
{
"id": 51,
"documentId": 10,
"templateId": null,
"email": "signer@example.com",
"name": "John Doe",
"token": "HkrptwS42ZBXdRKj1TyUo",
"documentDeletedAt": null,
"expired": null,
"signedAt": "2024-04-22T11:52:05.688Z",
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"signingOrder": 2,
"rejectionReason": null,
"role": "SIGNER",
"readStatus": "OPENED",
"signingStatus": "SIGNED",
"sendStatus": "SENT"
}
]
},
"createdAt": "2024-04-22T11:52:18.277Z",
"webhookEndpoint": "https://your-endpoint.com/webhook"
}
```
### `document.rejected`
Triggered when a recipient rejects the document.
**Event name:** `DOCUMENT_REJECTED`
The recipient's `signingStatus` changes to `REJECTED` and `rejectionReason` contains their reason.
```json
{
"event": "DOCUMENT_REJECTED",
"payload": {
"id": 10,
"externalId": null,
"userId": 1,
"authOptions": null,
"formValues": null,
"visibility": "EVERYONE",
"title": "contract.pdf",
"status": "PENDING",
"documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0",
"createdAt": "2024-04-22T11:44:43.341Z",
"updatedAt": "2024-04-22T11:48:07.569Z",
"completedAt": null,
"deletedAt": null,
"teamId": null,
"templateId": null,
"source": "DOCUMENT",
"documentMeta": {
"id": "doc_meta_123",
"subject": "Please sign this document",
"message": "Hello, please review and sign this document.",
"timezone": "UTC",
"password": null,
"dateFormat": "MM/DD/YYYY",
"redirectUrl": null,
"signingOrder": "PARALLEL",
"typedSignatureEnabled": true,
"language": "en",
"distributionMethod": "EMAIL",
"emailSettings": null
},
"Recipient": [
{
"id": 52,
"documentId": 10,
"templateId": null,
"email": "signer@example.com",
"name": "John Doe",
"token": "vbT8hi3jKQmrFP_LN1WcS",
"documentDeletedAt": null,
"expired": null,
"signedAt": "2024-04-22T11:48:07.569Z",
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"signingOrder": 1,
"rejectionReason": "I do not agree with the terms",
"role": "SIGNER",
"readStatus": "OPENED",
"signingStatus": "REJECTED",
"sendStatus": "SENT"
}
]
},
"createdAt": "2024-04-22T11:48:07.945Z",
"webhookEndpoint": "https://your-endpoint.com/webhook"
}
```
### `document.cancelled`
Triggered when the document owner cancels a pending document.
**Event name:** `DOCUMENT_CANCELLED`
```json
{
"event": "DOCUMENT_CANCELLED",
"payload": {
"id": 7,
"externalId": null,
"userId": 3,
"authOptions": null,
"formValues": null,
"visibility": "EVERYONE",
"title": "contract.pdf",
"status": "PENDING",
"documentDataId": "cm6exvn93006hi02ru90a265a",
"createdAt": "2025-01-27T11:02:14.393Z",
"updatedAt": "2025-01-27T11:03:16.387Z",
"completedAt": null,
"deletedAt": null,
"teamId": null,
"templateId": null,
"source": "DOCUMENT",
"documentMeta": {
"id": "cm6exvn96006ji02rqvzjvwoy",
"subject": "",
"message": "",
"timezone": "Etc/UTC",
"password": null,
"dateFormat": "yyyy-MM-dd hh:mm a",
"redirectUrl": "",
"signingOrder": "PARALLEL",
"typedSignatureEnabled": true,
"language": "en",
"distributionMethod": "EMAIL",
"emailSettings": null
},
"Recipient": [
{
"id": 7,
"documentId": 7,
"templateId": null,
"email": "signer@example.com",
"name": "John Doe",
"token": "XkKx1HCs6Znm2UBJA2j6o",
"documentDeletedAt": null,
"expired": null,
"signedAt": null,
"authOptions": { "accessAuth": null, "actionAuth": null },
"signingOrder": 1,
"rejectionReason": null,
"role": "SIGNER",
"readStatus": "NOT_OPENED",
"signingStatus": "NOT_SIGNED",
"sendStatus": "SENT"
}
]
},
"createdAt": "2025-01-27T11:03:27.730Z",
"webhookEndpoint": "https://your-endpoint.com/webhook"
}
```
---
## Recipient Events
Recipient events track individual signer actions. These events use the same payload structure as document events, but focus on a specific recipient's action.
### `document.opened`
Triggered when a recipient opens the document for the first time.
**Event name:** `DOCUMENT_OPENED`
The recipient's `readStatus` changes to `OPENED`.
```json
{
"event": "DOCUMENT_OPENED",
"payload": {
"id": 10,
"externalId": null,
"userId": 1,
"authOptions": null,
"formValues": null,
"visibility": "EVERYONE",
"title": "contract.pdf",
"status": "PENDING",
"documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0",
"createdAt": "2024-04-22T11:44:43.341Z",
"updatedAt": "2024-04-22T11:48:07.569Z",
"completedAt": null,
"deletedAt": null,
"teamId": null,
"templateId": null,
"source": "DOCUMENT",
"documentMeta": {
"id": "doc_meta_123",
"subject": "Please sign this document",
"message": "Hello, please review and sign this document.",
"timezone": "UTC",
"password": null,
"dateFormat": "MM/DD/YYYY",
"redirectUrl": null,
"signingOrder": "PARALLEL",
"typedSignatureEnabled": true,
"language": "en",
"distributionMethod": "EMAIL",
"emailSettings": null
},
"Recipient": [
{
"id": 52,
"documentId": 10,
"templateId": null,
"email": "signer@example.com",
"name": "John Doe",
"token": "vbT8hi3jKQmrFP_LN1WcS",
"documentDeletedAt": null,
"expired": null,
"signedAt": null,
"authOptions": null,
"signingOrder": 1,
"rejectionReason": null,
"role": "SIGNER",
"readStatus": "OPENED",
"signingStatus": "NOT_SIGNED",
"sendStatus": "SENT"
}
]
},
"createdAt": "2024-04-22T11:50:26.174Z",
"webhookEndpoint": "https://your-endpoint.com/webhook"
}
```
### `document.signed`
Triggered when a recipient signs the document.
**Event name:** `DOCUMENT_SIGNED`
The recipient's `signingStatus` changes to `SIGNED` and `signedAt` is populated.
```json
{
"event": "DOCUMENT_SIGNED",
"payload": {
"id": 10,
"externalId": null,
"userId": 1,
"authOptions": null,
"formValues": null,
"visibility": "EVERYONE",
"title": "contract.pdf",
"status": "COMPLETED",
"documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0",
"createdAt": "2024-04-22T11:44:43.341Z",
"updatedAt": "2024-04-22T11:52:05.708Z",
"completedAt": "2024-04-22T11:52:05.707Z",
"deletedAt": null,
"teamId": null,
"templateId": null,
"source": "DOCUMENT",
"documentMeta": {
"id": "doc_meta_123",
"subject": "Please sign this document",
"message": "Hello, please review and sign this document.",
"timezone": "UTC",
"password": null,
"dateFormat": "MM/DD/YYYY",
"redirectUrl": null,
"signingOrder": "PARALLEL",
"typedSignatureEnabled": true,
"language": "en",
"distributionMethod": "EMAIL",
"emailSettings": null
},
"Recipient": [
{
"id": 51,
"documentId": 10,
"templateId": null,
"email": "signer@example.com",
"name": "John Doe",
"token": "HkrptwS42ZBXdRKj1TyUo",
"documentDeletedAt": null,
"expired": null,
"signedAt": "2024-04-22T11:52:05.688Z",
"authOptions": {
"accessAuth": null,
"actionAuth": null
},
"signingOrder": 1,
"rejectionReason": null,
"role": "SIGNER",
"readStatus": "OPENED",
"signingStatus": "SIGNED",
"sendStatus": "SENT"
}
]
},
"createdAt": "2024-04-22T11:52:18.577Z",
"webhookEndpoint": "https://your-endpoint.com/webhook"
}
```
---
## Event Summary
| Event | Trigger | Key Changes |
| -------------------- | ------------------------------- | ------------------------------------------------------------ |
| `DOCUMENT_CREATED` | Document uploaded | `status: "DRAFT"` |
| `DOCUMENT_SENT` | Document sent to recipients | `status: "PENDING"`, recipients `sendStatus: "SENT"` |
| `DOCUMENT_OPENED` | Recipient opens document | Recipient `readStatus: "OPENED"` |
| `DOCUMENT_SIGNED` | Recipient signs document | Recipient `signingStatus: "SIGNED"`, `signedAt` set |
| `DOCUMENT_COMPLETED` | All recipients complete actions | `status: "COMPLETED"`, `completedAt` set |
| `DOCUMENT_REJECTED` | Recipient rejects document | Recipient `signingStatus: "REJECTED"`, `rejectionReason` set |
| `DOCUMENT_CANCELLED` | Owner cancels document | Document cancelled while pending |
---
## Handling Events
When processing webhook events:
{/* prettier-ignore */}
<Steps>
<Step>
**Verify the signature** — Check the `X-Documenso-Secret` header matches your configured secret
</Step>
<Step>
**Check event type** — Use the `event` field to determine the action
</Step>
<Step>
**Process idempotently** — Webhooks may be retried, so handle duplicate events
</Step>
<Step>
**Respond quickly** — Return a 200 status code within 30 seconds
</Step>
</Steps>
```typescript
app.post('/webhook', (req, res) => {
const secret = req.headers['x-documenso-secret'];
if (secret !== process.env.WEBHOOK_SECRET) {
return res.status(401).send('Unauthorized');
}
const { event, payload } = req.body;
switch (event) {
case 'DOCUMENT_COMPLETED':
// Handle completed document
console.log(`Document ${payload.id} completed`);
break;
case 'DOCUMENT_SIGNED':
// Handle signature
const signer = payload.Recipient.find((r) => r.signingStatus === 'SIGNED');
console.log(`${signer?.name} signed document ${payload.id}`);
break;
case 'DOCUMENT_REJECTED':
// Handle rejection
const rejecter = payload.Recipient.find((r) => r.signingStatus === 'REJECTED');
console.log(`${rejecter?.name} rejected: ${rejecter?.rejectionReason}`);
break;
}
res.status(200).send('OK');
});
```
---
## See Also
- [Webhook Setup](/docs/developers/webhooks/setup) - Configure webhook endpoints
- [Webhook Verification](/docs/developers/webhooks/verification) - Verify webhook signatures
@@ -0,0 +1,55 @@
---
title: Webhooks
description: Receive real-time notifications when documents are signed, completed, or updated.
---
## How Webhooks Work
1. You configure a webhook URL in Documenso
2. When an event occurs, Documenso sends an HTTP POST to your URL
3. Your application processes the event and responds with 200 OK
---
## Getting Started
<Cards>
<Card
title="Setup"
description="Configure webhook endpoints."
href="/docs/developers/webhooks/setup"
/>
<Card
title="Events"
description="Available webhook event types."
href="/docs/developers/webhooks/events"
/>
<Card
title="Verification"
description="Verify webhook signatures for security."
href="/docs/developers/webhooks/verification"
/>
</Cards>
---
## Example Payload
```json
{
"event": "DOCUMENT_COMPLETED",
"payload": {
"id": 123,
"title": "Contract",
"status": "COMPLETED"
},
"createdAt": "2024-01-15T10:30:00.000Z",
"webhookEndpoint": "https://your-endpoint.com/webhook"
}
```
---
## See Also
- [Document Lifecycle](/docs/concepts/document-lifecycle) - Understanding document statuses
@@ -0,0 +1,4 @@
{
"title": "Webhooks",
"pages": ["setup", "events", "verification"]
}
@@ -0,0 +1,355 @@
---
title: Webhook Setup
description: Configure webhooks to receive real-time notifications about document events.
---
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
## Overview
Webhooks are HTTP callbacks triggered by specific events in Documenso. When an event occurs (such as a document being signed), Documenso sends an HTTP POST request to your configured URL with details about the event.
Common use cases include:
- Syncing document status with your database
- Triggering automated workflows when documents are signed
- Integrating with CRM systems or other third-party services
- Sending custom notifications to stakeholders
<Callout type="info">
Webhooks are available for teams only. Personal accounts cannot configure webhooks.
</Callout>
## Creating a Webhook Endpoint
Before configuring a webhook in Documenso, you need an endpoint that can receive HTTP POST requests. Here's a minimal example:
<Tabs items={['Node.js (Express)', 'Python (Flask)', 'Go']}>
<Tab value="Node.js (Express)">
```javascript
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhooks/documenso', (req, res) => {
const { event, payload, createdAt } = req.body;
console.log(`Received event: ${event}`);
console.log(`Document ID: ${payload.id}`);
console.log(`Document title: ${payload.title}`);
// Process the webhook event
switch (event) {
case 'DOCUMENT_COMPLETED':
// Handle completed document
break;
case 'DOCUMENT_SIGNED':
// Handle signed document
break;
// Handle other events...
}
// Respond with 200 OK to acknowledge receipt
res.status(200).json({ received: true });
});
app.listen(3000, () => {
console.log('Webhook server running on port 3000');
});
````
</Tab>
<Tab value="Python (Flask)">
```python
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhooks/documenso', methods=['POST'])
def handle_webhook():
data = request.get_json()
event = data.get('event')
payload = data.get('payload')
print(f"Received event: {event}")
print(f"Document ID: {payload.get('id')}")
print(f"Document title: {payload.get('title')}")
# Process the webhook event
if event == 'DOCUMENT_COMPLETED':
# Handle completed document
pass
elif event == 'DOCUMENT_SIGNED':
# Handle signed document
pass
# Respond with 200 OK to acknowledge receipt
return jsonify({'received': True}), 200
if __name__ == '__main__':
app.run(port=3000)
````
</Tab>
<Tab value="Go">
```go
package main
import (
"encoding/json"
"fmt"
"net/http"
)
type WebhookPayload struct {
Event string `json:"event"`
Payload map[string]interface{} `json:"payload"`
CreatedAt string `json:"createdAt"`
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
var data WebhookPayload
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
http.Error(w, "Invalid payload", http.StatusBadRequest)
return
}
fmt.Printf("Received event: %s\n", data.Event)
fmt.Printf("Document ID: %v\n", data.Payload["id"])
fmt.Printf("Document title: %v\n", data.Payload["title"])
// Process the webhook event
switch data.Event {
case "DOCUMENT_COMPLETED":
// Handle completed document
case "DOCUMENT_SIGNED":
// Handle signed document
}
// Respond with 200 OK to acknowledge receipt
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]bool{"received": true})
}
func main() {
http.HandleFunc("/webhooks/documenso", webhookHandler)
fmt.Println("Webhook server running on port 3000")
http.ListenAndServe(":3000", nil)
}
```
</Tab>
</Tabs>
<Callout type="warn">
Always respond with a `200 OK` status within 30 seconds. Documenso will retry failed deliveries.
</Callout>
## Configuring Webhooks in Documenso via the Dashboard
{/* prettier-ignore */}
<Steps>
<Step>
### Navigate to team settings
Click your avatar in the top right corner and select **Team settings** from the dropdown menu.
</Step>
<Step>
### Open the webhooks tab
Navigate to the **Webhooks** tab in the team settings sidebar.
![Webhooks settings page](/webhook-images/webhooks-page.webp)
</Step>
<Step>
### Create a new webhook
Click the **Create Webhook** button to open the configuration dialog.
![Create webhook dialog](/webhook-images/create-webhook-dialog.webp)
</Step>
<Step>
### Configure the webhook
Fill in the following fields:
| Field | Description |
| ----- | ----------- |
| **Webhook URL** | The HTTPS endpoint that will receive webhook events |
| **Events** | Select which events should trigger this webhook |
| **Secret** (optional) | A secret key used to sign the payload for verification |
</Step>
<Step>
### Save the webhook
Click **Create Webhook** to save your configuration. The webhook is now active and will receive events.
</Step>
</Steps>
## Webhook URL Requirements
Your webhook endpoint must meet these requirements:
| Requirement | Details |
| ----------- | ------- |
| **Protocol** | HTTPS required (HTTP not allowed in production) |
| **Response** | Must return `2xx` status code within 30 seconds |
| **Method** | Must accept HTTP POST requests |
| **Content-Type** | Must accept `application/json` payloads |
| **Availability** | Must be publicly accessible from the internet |
<Callout type="info">
For local development, use a tunneling service like [ngrok](https://ngrok.com) or [localtunnel](https://localtunnel.me) to expose your local server.
</Callout>
## Selecting Events
When creating a webhook, you can subscribe to one or more events:
| Event | Trigger |
| ----- | ------- |
| `DOCUMENT_CREATED` | A new document is created |
| `DOCUMENT_SENT` | A document is sent to recipients |
| `DOCUMENT_OPENED` | A recipient opens the document |
| `DOCUMENT_SIGNED` | A recipient signs the document |
| `DOCUMENT_COMPLETED` | All recipients have signed the document |
| `DOCUMENT_REJECTED` | A recipient rejects the document |
| `DOCUMENT_CANCELLED` | The document owner cancels the document |
You can subscribe to all events or select specific ones based on your needs. For example, if you only need to know when documents are fully signed, subscribe only to `DOCUMENT_COMPLETED`.
See [Webhook Events](/docs/developers/webhooks/events) for detailed payload information for each event type.
## Testing Webhooks
Documenso provides a built-in testing feature to verify your webhook endpoint works correctly.
{/* prettier-ignore */}
<Steps>
<Step>
### Navigate to webhook details
Go to **Team Settings > Webhooks** and click on the webhook you want to test.
![Webhook detail page](/webhook-images/webhook-detail-page.webp)
</Step>
<Step>
### Click test
Click the **Test** button in the webhook details page.
</Step>
<Step>
### Select an event type
Choose which event type you want to simulate from the dropdown.
</Step>
<Step>
### Send test payload
Click **Send** to dispatch a test webhook with sample data to your endpoint.
![Webhook test trigger](/webhook-images/webhook-test-trigger.webp)
</Step>
</Steps>
The test payload contains realistic sample data so you can verify your endpoint processes events correctly. After sending, you can view the response in the webhook call logs.
### Viewing Webhook Logs
Each webhook subscription maintains a log of all delivery attempts. To view logs:
{/* prettier-ignore */}
<Steps>
<Step>
Go to **Team Settings > Webhooks**
</Step>
<Step>
Click on a webhook to view its details
</Step>
<Step>
Review the logs
Each webhook call shows the following details:
- Status (success/failure)
- Event type
- Timestamp
- Response code
- Request and response bodies
Click any call to see full details including headers and response data.
</Step>
</Steps>
### Resending Failed Webhooks
If a webhook delivery fails, you can manually resend it:
{/* prettier-ignore */}
<Steps>
<Step>
Navigate to the webhook call details page
</Step>
<Step>
Click the **Resend** button
</Step>
<Step>
Documenso will attempt to deliver the same payload again
</Step>
</Steps>
## Retry Policy
When a webhook delivery fails (non-2xx response or timeout), Documenso automatically retries with exponential backoff:
| Attempt | Delay |
| ------- | ----- |
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
After 5 failed attempts, the webhook is marked as failed and no further automatic retries occur. You can manually resend failed webhooks from the dashboard.
<Callout type="warn">
If your endpoint consistently fails, consider reviewing your server logs and ensuring your endpoint meets all [URL requirements](#webhook-url-requirements).
</Callout>
## Security Best Practices
<Accordions type="multiple">
<Accordion title="Use HTTPS">
Always use HTTPS endpoints in production.
</Accordion>
<Accordion title="Verify signatures">
Use a webhook secret and verify the `X-Documenso-Secret` header (see [Verification](/docs/developers/webhooks/verification)).
</Accordion>
<Accordion title="Validate payloads">
Validate incoming data before processing.
</Accordion>
<Accordion title="Respond quickly">
Return 200 OK immediately, then process asynchronously.
</Accordion>
<Accordion title="Idempotency">
Handle duplicate deliveries gracefully.
</Accordion>
</Accordions>
## Next Steps
- [Webhook Events](/docs/developers/webhooks/events) - Detailed payload structure for each event type
- [Webhook Verification](/docs/developers/webhooks/verification) - Secure your webhooks with signature verification
```
@@ -0,0 +1,290 @@
---
title: Webhook Verification
description: Verify webhook signatures for security.
---
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
## Overview
Verifying webhook requests ensures that incoming payloads originate from Documenso and have not been tampered with. Without verification, attackers could forge requests to your endpoint and trigger unintended actions.
## How Documenso Signs Webhooks
When you configure a webhook with a secret, Documenso includes that secret in every webhook request via the `X-Documenso-Secret` header. Your server should compare this header value against your stored secret to authenticate the request.
```bash
POST /your-webhook-endpoint HTTP/1.1
Host: your-server.com
Content-Type: application/json
X-Documenso-Secret: your_webhook_secret_here
{"event": "DOCUMENT_COMPLETED", "payload": {...}}
```
## Signature Header Format
| Header | Description |
| -------------------- | ------------------------------------------------------- |
| `X-Documenso-Secret` | The secret key you configured when creating the webhook |
The header contains your webhook secret as a plain string. If you did not configure a secret, the header will be an empty string.
## Verification Steps
{/* prettier-ignore */}
<Steps>
<Step>
Extract the `X-Documenso-Secret` header from the incoming request
</Step>
<Step>
Compare it against your stored webhook secret using a constant-time comparison
</Step>
<Step>
Reject the request if the values do not match
</Step>
<Step>
Process the webhook payload if verification succeeds
</Step>
</Steps>
<Callout type="warn">
Always use constant-time string comparison to prevent timing attacks. Standard equality operators
(`===` or `==`) can leak information about the secret through response time variations.
</Callout>
## Code Examples
<Tabs items={['Node.js (Express)', 'Python (Flask)']}>
<Tab value="Node.js (Express)">
```javascript
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
const WEBHOOK_SECRET = process.env.DOCUMENSO_WEBHOOK_SECRET;
function verifyWebhookSignature(receivedSecret, expectedSecret) {
if (!expectedSecret) {
// No secret configured, skip verification
// Not recommended for production
return true;
}
if (!receivedSecret) {
return false;
}
// Use constant-time comparison to prevent timing attacks
try {
return crypto.timingSafeEqual(
Buffer.from(receivedSecret),
Buffer.from(expectedSecret),
);
} catch {
return false;
}
}
app.post('/webhooks/documenso', (req, res) => {
const receivedSecret = req.headers['x-documenso-secret'];
if (!verifyWebhookSignature(receivedSecret, WEBHOOK_SECRET)) {
console.error('Webhook verification failed');
return res.status(401).json({ error: 'Invalid signature' });
}
// Signature verified, process the webhook
const { event, payload } = req.body;
console.log(`Verified webhook: ${event}`);
// Process the event...
res.status(200).json({ received: true });
});
app.listen(3000, () => {
console.log('Webhook server running on port 3000');
});
````
</Tab>
<Tab value="Python (Flask)">
```python
import hmac
import os
from flask import Flask, request, jsonify
app = Flask(__name__)
WEBHOOK_SECRET = os.environ.get('DOCUMENSO_WEBHOOK_SECRET')
def verify_webhook_signature(received_secret, expected_secret):
"""Verify the webhook signature using constant-time comparison."""
if not expected_secret:
# No secret configured, skip verification
# Not recommended for production
return True
if not received_secret:
return False
# Use constant-time comparison to prevent timing attacks
return hmac.compare_digest(received_secret, expected_secret)
@app.route('/webhooks/documenso', methods=['POST'])
def handle_webhook():
received_secret = request.headers.get('X-Documenso-Secret', '')
if not verify_webhook_signature(received_secret, WEBHOOK_SECRET):
print('Webhook verification failed')
return jsonify({'error': 'Invalid signature'}), 401
# Signature verified, process the webhook
data = request.get_json()
event = data.get('event')
payload = data.get('payload')
print(f'Verified webhook: {event}')
# Process the event...
return jsonify({'received': True}), 200
if __name__ == '__main__':
app.run(port=3000)
````
</Tab>
</Tabs>
## Handling Verification Failures
When verification fails, follow these practices:
| Action | Description |
| ------------------- | ------------------------------------------------------------------ |
| Return 401 status | Respond with `401 Unauthorized` to indicate authentication failure |
| Log the attempt | Record failed attempts for security monitoring |
| Do not process | Never process the payload if verification fails |
| Do not leak details | Avoid exposing information about why verification failed |
```javascript
app.post('/webhooks/documenso', (req, res) => {
const receivedSecret = req.headers['x-documenso-secret'];
if (!verifyWebhookSignature(receivedSecret, WEBHOOK_SECRET)) {
// Log for monitoring but don't expose details
console.error('Webhook verification failed', {
timestamp: new Date().toISOString(),
ip: req.ip,
});
// Generic error response
return res.status(401).json({ error: 'Unauthorized' });
}
// Continue processing...
});
```
### Common Verification Issues
| Issue | Cause | Solution |
| --------------- | --------------------------------------- | ------------------------------------------------------------------ |
| Secret mismatch | Webhook secret changed or misconfigured | Verify the secret in your environment matches the one in Documenso |
| Empty header | Webhook created without a secret | Add a secret to the webhook configuration in Documenso |
| Encoding issues | String encoding mismatch | Ensure both secrets use the same encoding (UTF-8) |
## Security Best Practices
<Accordions type="multiple">
<Accordion title="Use strong secrets">
Generate a cryptographically secure random string for your webhook secret:
```bash
# Generate a 32-byte random secret
openssl rand -hex 32
```
</Accordion>
<Accordion title="Store secrets securely">
Never hardcode secrets in your source code. Use environment variables or a secrets manager:
```javascript
// Good: Environment variable
const WEBHOOK_SECRET = process.env.DOCUMENSO_WEBHOOK_SECRET;
// Bad: Hardcoded
const WEBHOOK_SECRET = 'my-secret-key'; // Never do this
```
</Accordion>
<Accordion title="Rotate secrets periodically">
Update your webhook secret periodically:
1. Generate a new secret
2. Update your server to accept both old and new secrets temporarily
3. Update the webhook configuration in Documenso
4. Remove the old secret from your server
</Accordion>
<Accordion title="Validate payload structure">
After verifying the signature, validate the payload structure before processing:
```javascript
const { event, payload, createdAt } = req.body;
if (!event || !payload) {
return res.status(400).json({ error: 'Invalid payload structure' });
}
// Validate event is a known type
const validEvents = [
'DOCUMENT_CREATED',
'DOCUMENT_SENT',
'DOCUMENT_OPENED',
'DOCUMENT_SIGNED',
'DOCUMENT_COMPLETED',
'DOCUMENT_REJECTED',
'DOCUMENT_CANCELLED',
];
if (!validEvents.includes(event)) {
console.warn(`Unknown event type: ${event}`);
}
```
</Accordion>
<Accordion title="Use HTTPS">
Always use HTTPS endpoints in production to encrypt data in transit, including the secret header.
</Accordion>
<Accordion title="Implement rate limiting">
Protect your endpoint from abuse with rate limiting:
```javascript
const rateLimit = require('express-rate-limit');
const webhookLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 100, // 100 requests per minute
message: { error: 'Too many requests' },
});
app.post('/webhooks/documenso', webhookLimiter, (req, res) => {
// Handle webhook...
});
```
</Accordion>
</Accordions>
## See Also
- [Webhook Setup](/docs/developers/webhooks/setup) - Configure webhook endpoints and secrets
- [Webhook Events](/docs/developers/webhooks/events) - Event types and payload structure