mirror of
https://github.com/documenso/documenso.git
synced 2026-07-13 06:25:01 +10:00
feat: docs v2 (#2460)
Co-authored-by: Catalin Pit <catalinpit@gmail.com>
This commit is contained in:
@@ -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
|
||||
```
|
||||
|
||||

|
||||
|
||||
```bash
|
||||
# New editor
|
||||
|
||||
https://app.documenso.com/t/<team-url>/documents/<envelope-id>/edit?step=addFields&devmode=true
|
||||
```
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||

|
||||
|
||||
</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
|
||||
|
||||

|
||||
|
||||
</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}
|
||||
```
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||

|
||||
|
||||
</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.
|
||||
|
||||

|
||||
|
||||
</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.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||

|
||||
|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
### Navigate to the API Tokens tab
|
||||
|
||||
Go to **Settings** and open the **API Tokens** tab.
|
||||
|
||||

|
||||
|
||||
</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.
|
||||
|
||||

|
||||
|
||||
<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.
|
||||
|
||||
[](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.
|
||||
|
||||

|
||||
</Step>
|
||||
|
||||
<Step>
|
||||
### Create a new webhook
|
||||
|
||||
Click the **Create Webhook** button to open the configuration dialog.
|
||||
|
||||

|
||||
</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.
|
||||
|
||||

|
||||
</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.
|
||||
|
||||

|
||||
</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
|
||||
Reference in New Issue
Block a user