@railblocks/docusign-convex-component
v0.1.6
Published
A docusign integration component for Convex.
Readme
DocuSign Convex Component
A reusable Convex component for integrating DocuSign eSignature into your Convex applications.
Features
- 🔐 OAuth 2.0 Authentication - Secure connection to DocuSign accounts
- 📄 Envelope Management - Create, send, and track envelopes
- 📋 Template Support - Create envelopes from templates with prefilled tabs
- 🔔 Webhook Events - Automatically track all envelope status changes
- 🔄 Token Refresh - Automatic access token refresh handling
- 💾 Database Storage - Stores envelopes and events in Convex tables
Installation
npm install @railblocks/docusign-convex-componentGet Started
Add the component to your convex/convex.config.ts:
import { defineApp } from "convex/server";
import docusignIntegration from "@railblocks/docusign-convex-component/convex.config";
const app = defineApp();
app.use(docusignIntegration);
export default app;Set environment variables in your Convex dashboard:
DOCUSIGN_INTEGRATION_KEY=your_integration_key
DOCUSIGN_SECRET_KEY=your_secret_key
ENCRYPTION_KEY=your_32_byte_encryption_key_in_hex # Generate with: openssl rand -hex 32
DOCUSIGN_ENVIRONMENT=demo # or "production"
DOCUSIGN_HMAC_KEY=your_webhook_hmac_key # Optional but recommendedCreate a DocuSign instance in convex/docusign.ts:
import { components } from "./_generated/api";
import { DocuSign } from "@railblocks/docusign-convex-component";
export const docusign = new DocuSign(components.docusignIntegration, {});Register HTTP routes in convex/http.ts:
import { httpRouter } from "convex/server";
import { registerDocuSignRoutes } from "@railblocks/docusign-convex-component";
import { docusign } from "./docusign";
const http = httpRouter();
registerDocuSignRoutes(http, docusign);
export default http;This registers:
GET /docusign/oauth/callback- OAuth callbackPOST /docusign/webhooks- Webhook receiver
Quick Start
1. Set Up Connection
Option A: Using Existing Tokens (Recommended for Single-User Apps)
import { action } from "./_generated/server";
import { docusign } from "./docusign";
export const setupDocuSign = action({
handler: async ctx => {
return await docusign.setupWithTokens(
ctx,
"my-company-id",
"YOUR_ACCESS_TOKEN",
"YOUR_REFRESH_TOKEN",
);
},
});Option B: OAuth Flow (For Multi-User Apps)
export const getAuthUrl = action({
handler: async ctx => {
const userId = "user_123"; // Your auth logic
return await docusign.initiateOAuth(
ctx,
userId,
"http://localhost:3000/docusign/oauth/callback",
);
},
});2. Create and Send an Envelope
import { internalAction } from "./_generated/server";
import { docusign } from "./docusign";
export const sendDocument = internalAction({
handler: async ctx => {
return await docusign.createEnvelope(ctx, {
userId: "user_123",
emailSubject: "Please sign this document",
recipients: [
{
email: "[email protected]",
name: "John Doe",
recipientId: "1",
},
],
documents: [
{
documentBase64: "...", // Your base64 encoded PDF
name: "Contract.pdf",
fileExtension: "pdf",
documentId: "1",
},
],
status: "sent", // or "created" to send later
});
},
});3. Create from Template
export const createFromTemplate = internalAction({
handler: async ctx => {
return await docusign.createFromTemplate(ctx, {
userId: "user_123",
templateId: "your-template-id",
emailSubject: "Please sign",
templateRoles: [
{
email: "[email protected]",
name: "John Doe",
roleName: "Signer",
tabs: {
textTabs: [
{ tabLabel: "name", value: "John Doe" },
{ tabLabel: "date", value: "2024-01-15" },
],
},
},
],
});
},
});Query Envelopes
import { internalQuery } from "./_generated/server";
import { docusign } from "./docusign";
export const getEnvelope = internalQuery({
handler: async (ctx, args: { userId: string; envelopeId: string }) => {
return await docusign.getEnvelope(ctx, args.userId, args.envelopeId);
},
});
export const listEnvelopes = internalQuery({
handler: async (ctx, args: { userId: string }) => {
return await docusign.listEnvelopes(ctx, args.userId);
},
});Advanced Usage
Configure Webhooks in DocuSign
- In DocuSign Settings → Connect → Add Configuration
- Set webhook URL:
https://your-app.convex.site/docusign/webhooks - Select events: Envelope Sent, Delivered, Completed, Declined, Voided
The component automatically stores events and updates envelope statuses.
Send an Envelope Created with status="created"
export const sendEnvelope = internalAction({
handler: async (ctx, args: { userId: string; envelopeId: string }) => {
return await docusign.sendEnvelope(ctx, args.userId, args.envelopeId);
},
});Void an Envelope
export const voidEnvelope = internalAction({
handler: async (ctx, args: { userId: string; envelopeId: string }) => {
return await docusign.voidEnvelope(ctx, args.userId, args.envelopeId, "No longer needed");
},
});Custom API Calls
Need to call a DocuSign endpoint not covered by the built-in methods?
export const listTemplates = action({
handler: async ctx => {
return await docusign.makeAPICall(ctx, "user_123", "/templates", "GET");
},
});See DocuSign REST API docs for all available endpoints.
DocuSign Component Options
const docusign = new DocuSign(components.docusignIntegration, {
integrationKey: "...", // Optional, reads from env
secretKey: "...", // Optional, reads from env
encryptionKey: "...", // Required for security
environment: "production", // "demo" or "production"
hmacKey: "...", // For webhook validation
});API Reference
OAuth
docusign.initiateOAuth(ctx, userId, redirectUri, state?)- Get authorization URLdocusign.setupWithTokens(ctx, userId, accessToken, refreshToken)- Use existing tokens
Envelopes
docusign.createEnvelope(ctx, options)- Create envelope with documentsdocusign.createEmptyEnvelope(ctx, options)- Create empty envelopedocusign.createFromTemplate(ctx, options)- Create from templatedocusign.sendEnvelope(ctx, userId, envelopeId)- Send an envelopedocusign.voidEnvelope(ctx, userId, envelopeId, reason)- Void an envelopedocusign.syncEnvelopeStatus(ctx, userId, envelopeId)- Sync status from DocuSigndocusign.getEnvelope(ctx, userId, envelopeId)- Get envelope from databasedocusign.listEnvelopes(ctx, userId, paginationOpts?)- List user's envelopes
Webhooks
docusign.handleDocuSignWebhook(ctx, req)- Handle webhook (used internally)docusign.getEnvelopeEvents(ctx, userId, envelopeId)- Get all events for an envelopedocusign.getLatestEvent(ctx, userId, envelopeId)- Get latest event
Custom
docusign.makeAPICall(ctx, userId, path, method?, body?, headers?)- Make any DocuSign API call
Security
- ENCRYPTION_KEY is required - OAuth tokens are encrypted at rest
- DOCUSIGN_HMAC_KEY is strongly recommended - validates webhook authenticity
- Generate encryption key:
openssl rand -hex 32
License
MIT
Support
{
email: "[email protected]",
name: "John Doe",
recipientId: "1",
},
],
status: "created",
},
);}, });
### 3. Create an Envelope from Template
> **Important:** The `tabLabel` must match the **Data Label** field you set in your DocuSign template for each field. You can find this in the DocuSign template editor under the field's properties.
```ts
import { internalAction } from "./_generated/server";
import { internal } from "./_generated/api";
export const createFromTemplate = internalAction({
args: {},
handler: async (ctx) => {
const userId = "user_123";
return await ctx.runAction(
internal.docusignIntegration.envelopes.createFromTemplate,
{
userId,
templateId: "your-template-id",
emailSubject: "Please sign this document",
templateRoles: [
{
email: "[email protected]",
name: "John Doe",
roleName: "Signer", // Must match role name in template
tabs: {
textTabs: [
// tabLabel must match the "Data Label" in your template
{ tabLabel: "name", value: "John Doe" },
{ tabLabel: "date", value: "2024-01-15" },
],
},
},
],
status: "created",
},
);
},
});4. Send an Envelope
import { internalAction } from "./_generated/server";
import { v } from "convex/values";
import { internal } from "./_generated/api";
export const sendEnvelope = internalAction({
args: { envelopeId: v.string() },
handler: async (ctx, args) => {
const userId = "user_123";
return await ctx.runAction(internal.docusignIntegration.envelopes.sendEnvelope, {
userId,
envelopeId: args.envelopeId,
});
},
});5. Query Envelopes
import { internalQuery } from "./_generated/server";
import { v } from "convex/values";
import { internal } from "./_generated/api";
export const getEnvelope = internalQuery({
args: { envelopeId: v.string() },
handler: async (ctx, args) => {
return await ctx.runQuery(internal.docusignIntegration.envelopes.getEnvelope, {
envelopeId: args.envelopeId,
});
},
});
export const listEnvelopes = internalQuery({
args: { userId: v.string() },
handler: async (ctx, args) => {
return await ctx.runQuery(internal.docusignIntegration.envelopes.listEnvelopes, {
userId: args.userId,
});
},
});Webhook Configuration
To receive webhook events from DocuSign:
- In DocuSign Settings → Connect → Add Configuration
- Set the webhook URL to:
https://your-app.convex.site/docusign/webhooks - Select the envelope events you want to track:
- Envelope Sent (event:
envelope-sent, status:sent) - Envelope Delivered (event:
envelope-delivered, status:delivered) - Envelope Completed (event:
envelope-completed, status:completed) - Envelope Declined (event:
envelope-declined, status:declined) - Envelope Voided (event:
envelope-voided, status:voided) - Recipient Completed (event:
recipient-completed)
- Envelope Sent (event:
- (Recommended) Enable HMAC signature validation in Connect settings
Important: DocuSign uses different naming conventions:
- Webhook event types use hyphenated format (e.g.,
envelope-sent) - Envelope status field uses lowercase (e.g.,
sent) - The component automatically handles the conversion
The component will automatically store all events and update envelope statuses in your database.
API Reference
See the complete example in example/convex/example.ts.
Main Functions
OAuth (Public)
oauth.initiateOAuth- Get authorization URL (public action)oauth.setupWithTokens- Set up connection with existing access/refresh tokens (public action)
OAuth (Internal)
oauth.completeOAuth- Complete OAuth flow (called by callback)oauth.getValidConnection- Get valid connection with auto-refresh
Envelopes (Internal)
All envelope functions are internal for security:
envelopes.createEnvelopeWithDocuments- Create envelope with PDF/document attachmentsenvelopes.createEmptyEnvelope- Create empty envelope (for templates or manual document addition)envelopes.createFromTemplate- Create from template with prefilled tabsenvelopes.sendEnvelope- Send an envelopeenvelopes.voidEnvelope- Void an envelopeenvelopes.syncEnvelopeStatus- Sync status from DocuSignenvelopes.getEnvelope- Get envelope from databaseenvelopes.listEnvelopes- List user's envelopes (with pagination)
Webhooks (Public Queries)
webhooks.getEnvelopeEvents- Get all events for an envelopewebhooks.getLatestEvent- Get latest event for an envelope
Custom API Calls
custom.makeAPICall- Make any DocuSign REST API call not covered by built-in functionscustom.getTemplate- Get template details including all available tabs/fieldscustom.listTemplates- List all templates in the accountcustom.getTemplateTabs- Get all tabs for a specific recipient in a template
Discovering Template Tabs
Before using a template, you can discover what Data Labels (tab labels) are available:
import { action } from "./_generated/server";
import { docusign } from "./docusign";
export const inspectTemplate = action({
args: { templateId: v.string() },
handler: async (ctx, args) => {
// Get full template with all tabs
const template = await docusign.getTemplate(ctx, "user_123", args.templateId);
// Or get tabs for a specific recipient
const tabs = await docusign.getTemplateTabs(ctx, "user_123", args.templateId, "1");
// See what Data Labels are available
console.log(
"Available text fields:",
tabs.textTabs?.map(t => t.tabLabel),
);
console.log(
"Available checkboxes:",
tabs.checkboxTabs?.map(t => t.tabLabel),
);
return tabs;
},
});Custom API Calls
Need to call a DocuSign endpoint not covered by the built-in functions? Use the custom.makeAPICall
action:
import { action } from "./_generated/server";
import { components } from "./_generated/api";
// List all templates
export const listTemplates = action({
args: {},
handler: async ctx => {
const userId = "user_123"; // Your auth logic
return await ctx.runAction(components.docusignIntegration.custom.makeAPICall, {
userId,
path: "/templates",
method: "GET",
});
},
});
// Get envelope documents
export const getEnvelopeDocuments = action({
args: { envelopeId: v.string() },
handler: async (ctx, args) => {
const userId = "user_123";
return await ctx.runAction(components.docusignIntegration.custom.makeAPICall, {
userId,
path: `/envelopes/${args.envelopeId}/documents`,
method: "GET",
});
},
});
// Create a brand
export const createBrand = action({
args: { brandName: v.string() },
handler: async (ctx, args) => {
const userId = "user_123";
return await ctx.runAction(components.docusignIntegration.custom.makeAPICall, {
userId,
path: "/brands",
method: "POST",
body: {
brandName: args.brandName,
defaultBrandLanguage: "en",
},
});
},
});The makeAPICall function:
- Automatically handles OAuth authentication and token refresh
- Supports relative paths (e.g.,
/templates) or absolute paths - Accepts any HTTP method (GET, POST, PUT, DELETE, etc.)
- Passes through request body and headers
- Returns the raw JSON response from DocuSign
See DocuSign REST API documentation for all available endpoints.
Development
Run the example app:
npm install
npm run devLicense
MIT
