E-Signature API Tutorial: Sign PDFs in Node.js, Python, PHP, Go, Java & Ruby

2026-10-31来源 dev.to PythonEnglish

Almost every SaaS app eventually needs a signature: a contract, an NDA, an onboarding form, a statement of work. Building it yourself means field placement on PDFs, signer emails, ordering, tamper-proof audit trails, and a legal story you'd rather not own.

An e-signature API (you'll also see it called an electronic signature API or digital signature API) turns that into three calls:

  1. Send a PDF to one or more signers
  2. Get notified when they sign (webhook)
  3. Download the signed PDF

You can get there two ways:

  • The shortcut: install one agent skill and let your AI coding agent do the setup (next section)
  • By hand: sign PDFs from code with the SDK in Node.js, TypeScript, Python, PHP, Go, Java, and Ruby (the rest of this post)

The shortcut: a Claude Code skill (also works in Cursor and Copilot)

If you already work with an AI coding agent, you can skip the docs entirely. We publish an open-source Agent Skill that teaches your agent how to install and wire up the SDK for your project:

npx skills add TurboDocx/quickstart 

The installer is the open-source skills CLI. It asks which skills you want and writes them where your agent looks for them (.claude/skills/, .cursor/skills/, .github/skills/, and so on). You need Node.js 18+ for the installer only; the skill itself targets whatever language your project uses.

It works with Claude Code, Cursor, GitHub Copilot, OpenCode, OpenAI Codex CLI, and Gemini CLI, since it follows the open Agent Skill spec instead of tying you to one vendor.

You get two skills in one install:

  • /turbodocx-sdk: e-signatures, signature webhooks, embedded signing, document generation, and quotes in JavaScript/TypeScript, Python, Go, PHP, Java, or Ruby
  • /turbodocx-html-to-docx: HTML to Word documents in any Node.js project (Express, Next.js, Fastify, NestJS, or plain Node)

On Claude Code you can also install it as a plugin with claude plugin add TurboDocx/quickstart.

After that, just ask in plain English:

Add TurboSign e-signatures to my Express app 
Add a /signatures/:id/download endpoint that returns the signed PDF 

Or skip the questions and go straight to what you need:

/turbodocx-sdk turbosign # send for signature, status, download /turbodocx-sdk turbowebhooks # webhook subscription and a verified handler 

Here's what the skill does, in order, so there's no magic involved:

  1. Detect: reads package.json, pyproject.toml or requirements.txt, go.mod, composer.json, pom.xml or build.gradle, or Gemfile to find your language, framework, and package manager before touching a file
  2. Ask: checks what you want (TurboSign for signatures, Deliverable for document generation, or both). Webhooks, quotes, and partner provisioning are opt-in
  3. Install: runs the right command for your package manager (npm, pnpm, yarn, bun, pip, poetry, pipenv, go get, composer, Maven, Gradle, Bundler)
  4. Generate: writes a config file and route handlers that follow your project's patterns, wires them into your app, and reads your API key from .env instead of hardcoding it
  5. Verify: checks the files exist and compile (running tsc for TypeScript), then lists what it created, which env vars to fill in, and a curl command to test the first endpoint

If you don't have an API key yet, grab one from the free plan at app.turbodocx.com and paste it into your .env.

Review what your agent writes. Agent-generated code is a starting point, not a finished feature. In particular, if you ask for a webhook handler, check it against the raw-body rule in Step 2 below. That's the part people (and agents) most often get subtly wrong.

The skill is MIT licensed and lives at github.com/TurboDocx/quickstart.

Prefer to do it by hand? Here's the whole flow

Pick your language, copy, run. Each block installs that language's e-signature SDK and sends a PDF for signature. It's the same SDK code the skill wires into your app, so it's worth reading once to know what you're shipping.

Setup (same for every language)

  1. Create a free account at app.turbodocx.com
  2. Go to Settings → API Keys and copy your API key and Organization ID
  3. Put the credentials in your environment:
TURBODOCX_API_KEY=your_api_key_here TURBODOCX_ORG_ID=your_org_id_here TURBODOCX_SENDER_EMAIL=you@company.com # required, used as the reply-to on signing emails TURBODOCX_SENDER_NAME=Your Company Name TURBODOCX_WEBHOOK_SECRET=your_webhook_secret_here # you get this in Step 2, when you create the webhook 

How field placement works

You don't need pixel coordinates. Put an anchor like {signature1} in your PDF where the signature should go, then tell the API to replace it:

{ "type": "signature", "recipientEmail": "john@example.com", "template": { "anchor": "{signature1}", "placement": "replace", "size": { "width": 100, "height": 30 } } } 

If you regenerate the contract and the layout shifts, the field moves with the text. Same idea works for initials, dates, text boxes, and checkboxes. If editing anchor text into PDFs gets annoying, skip ahead to drag and drop with signer roles.

Step 1: Send a PDF for signature with the e-signature API

Node.js (JavaScript)
npm install @turbodocx/sdk 

Requires Node.js 18+.

import { TurboSign } from '@turbodocx/sdk'; import { readFileSync } from 'fs'; TurboSign.configure({ apiKey: process.env.TURBODOCX_API_KEY, orgId: process.env.TURBODOCX_ORG_ID, senderEmail: process.env.TURBODOCX_SENDER_EMAIL, // required senderName: process.env.TURBODOCX_SENDER_NAME, }); const result = await TurboSign.sendSignature({ file: readFileSync('contract.pdf'), documentName: 'Partnership Agreement', recipients: [ { name: 'John Doe', email: 'john@example.com', signingOrder: 1 }, ], fields: [ { type: 'signature', recipientEmail: 'john@example.com', template: { anchor: '{signature1}', placement: 'replace', size: { width: 100, height: 30 }, }, }, ], }); console.log('Document ID:', result.documentId); 

TypeScript

Types ship inside the package, so there's no @types install. Use NodeNext module resolution and esModuleInterop.

npm install @turbodocx/sdk 
import { TurboSign, type HttpClientConfig, type Recipient, type Field, type SendSignatureResponse, } from '@turbodocx/sdk'; import { readFileSync } from 'fs'; const config: HttpClientConfig = { apiKey: process.env.TURBODOCX_API_KEY, orgId: process.env.TURBODOCX_ORG_ID, senderEmail: process.env.TURBODOCX_SENDER_EMAIL, // required senderName: process.env.TURBODOCX_SENDER_NAME, }; TurboSign.configure(config); const recipients: Recipient[] = [ { name: 'John Doe', email: 'john@example.com', signingOrder: 1 }, ]; const fields: Field[] = [ { type: 'signature', recipientEmail: 'john@example.com', template: { anchor: '{signature1}', placement: 'replace', size: { width: 100, height: 30 }, }, }, ]; const result: SendSignatureResponse = await TurboSign.sendSignature({ file: readFileSync('contract.pdf'), documentName: 'Partnership Agreement', recipients, fields, }); console.log('Document ID:', result.documentId); 

An invalid field type (or a misspelled placement) fails at compile time instead of in production.


Python

Python 3.9+. The SDK is async-native (built on httpx), so it drops straight into FastAPI or Django async views.

pip install turbodocx-sdk 
import asyncio import os from turbodocx_sdk import TurboSign async def main(): TurboSign.configure( api_key=os.getenv("TURBODOCX_API_KEY"), org_id=os.getenv("TURBODOCX_ORG_ID"), sender_email=os.getenv("TURBODOCX_SENDER_EMAIL"), # required  sender_name=os.getenv("TURBODOCX_SENDER_NAME"), ) with open("contract.pdf", "rb") as f: pdf_file = f.read() result = await TurboSign.send_signature( file=pdf_file, document_name="Partnership Agreement", recipients=[ {"name": "John Doe", "email": "john@example.com", "signingOrder": 1} ], fields=[ { "type": "signature", "recipientEmail": "john@example.com", "template": { "anchor": "{signature1}", "placement": "replace", "size": {"width": 100, "height": 30}, }, } ], ) print(f"Document ID: {result['documentId']}") asyncio.run(main()) 

PHP

PHP 8.1+ (enums and named parameters), works in Laravel, Symfony, or plain PHP.

composer require turbodocx/sdk 
<?php use TurboDocx\TurboSign; use TurboDocx\Config\HttpClientConfig; use TurboDocx\Types\Recipient; use TurboDocx\Types\Field; use TurboDocx\Types\SignatureFieldType; use TurboDocx\Types\TemplateConfig; use TurboDocx\Types\FieldPlacement; use TurboDocx\Types\Requests\SendSignatureRequest; TurboSign::configure(new HttpClientConfig( apiKey: getenv('TURBODOCX_API_KEY'), orgId: getenv('TURBODOCX_ORG_ID'), senderEmail: getenv('TURBODOCX_SENDER_EMAIL'), // required senderName: getenv('TURBODOCX_SENDER_NAME') )); $result = TurboSign::sendSignature( new SendSignatureRequest( recipients: [ new Recipient('John Doe', 'john@example.com', 1), ], fields: [ new Field( type: SignatureFieldType::SIGNATURE, recipientEmail: 'john@example.com', template: new TemplateConfig( anchor: '{signature1}', placement: FieldPlacement::REPLACE, size: ['width' => 100, 'height' => 30] ) ), ], file: file_get_contents('contract.pdf'), documentName: 'Partnership Agreement' ) ); echo "Document ID: {$result->documentId}\n"; 

Go

Go 1.21+, standard library only, context.Context on every call.

go get github.com/TurboDocx/SDK/packages/go-sdk 
package main import ( "context" "fmt" "log" "os" turbodocx "github.com/TurboDocx/SDK/packages/go-sdk" ) func main() { client, err := turbodocx.NewClientWithConfig(turbodocx.ClientConfig{ APIKey: os.Getenv("TURBODOCX_API_KEY"), OrgID: os.Getenv("TURBODOCX_ORG_ID"), SenderEmail: os.Getenv("TURBODOCX_SENDER_EMAIL"), // required SenderName: os.Getenv("TURBODOCX_SENDER_NAME"), }) if err != nil { log.Fatal(err) } pdfFile, err := os.ReadFile("contract.pdf") if err != nil { log.Fatal(err) } result, err := client.TurboSign.SendSignature(context.Background(), &turbodocx.SendSignatureRequest{ File: pdfFile, FileName: "contract.pdf", DocumentName: "Partnership Agreement", Recipients: []turbodocx.Recipient{ {Name: "John Doe", Email: "john@example.com", SigningOrder: 1}, }, Fields: []turbodocx.Field{ { Type: "signature", RecipientEmail: "john@example.com", Template: &turbodocx.TemplateAnchor{ Anchor: "{signature1}", Placement: "replace", Size: &turbodocx.Size{Width: 100, Height: 30}, }, }, }, }) if err != nil { log.Fatal(err) } fmt.Printf("Document ID: %s\n", result.DocumentID) } 

Java

Java 11+, available on Maven Central, fluent builders.

<dependency> <groupId>com.turbodocx</groupId> <artifactId>turbodocx-sdk</artifactId> <version>0.9.0</version> </dependency> 
import com.turbodocx.TurboDocxClient; import com.turbodocx.models.*; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Arrays; public class Main { public static void main(String[] args) throws Exception { TurboDocxClient client = new TurboDocxClient.Builder() .apiKey(System.getenv("TURBODOCX_API_KEY")) .orgId(System.getenv("TURBODOCX_ORG_ID")) .senderEmail(System.getenv("TURBODOCX_SENDER_EMAIL")) // required .senderName(System.getenv("TURBODOCX_SENDER_NAME")) .build(); byte[] pdfFile = Files.readAllBytes(Paths.get("contract.pdf")); SendSignatureResponse result = client.turboSign().sendSignature( new SendSignatureRequest.Builder() .file(pdfFile) .fileName("contract.pdf") .documentName("Partnership Agreement") .recipients(Arrays.asList( new Recipient("John Doe", "john@example.com", 1) )) .fields(Arrays.asList( new Field.Builder() .type("signature") .recipientEmail("john@example.com") .template(new Field.TemplateAnchor.Builder() .anchor("{signature1}") .placement("replace") .size(new Field.Size(100, 30)) .build()) .build() )) .build() ); System.out.println("Document ID: " + result.getDocumentId()); } } 

Ruby

Ruby 2.7+. Works in Rails, Sinatra, or plain Ruby.

gem install turbodocx-sdk 

Or add gem "turbodocx-sdk" to your Gemfile and run bundle install.

require "turbodocx_sdk" TurboDocxSdk::TurboSign.configure( api_key: ENV.fetch("TURBODOCX_API_KEY"), org_id: ENV.fetch("TURBODOCX_ORG_ID"), sender_email: ENV.fetch("TURBODOCX_SENDER_EMAIL"), # required sender_name: ENV.fetch("TURBODOCX_SENDER_NAME") ) result = TurboDocxSdk::TurboSign.send_signature( "file" => "contract.pdf", # a file path or any IO "documentName" => "Partnership Agreement", "recipients" => [ { "name" => "John Doe", "email" => "john@example.com", "signingOrder" => 1 } ], "fields" => [ { "type" => "signature", "recipientEmail" => "john@example.com", "template" => { "anchor" => "{signature1}", "placement" => "replace", "size" => { "width" => 100, "height" => 30 } } } ] ) puts "Document ID: #{result['documentId']}" 

Method names are snake_case, but the request keys stay camelCase (documentName, recipientEmail) to match the API.


John gets an email with a signing link. No UI to build on your side. To check progress without a webhook, poll getStatus (for example under_review, completed, voided, or expired).

Agent shortcut: this is the code /turbodocx-sdk turbosign writes for you, wired into your framework's routes (Express, FastAPI, Laravel, Spring Boot, and so on) with your keys read from .env. Install it with npx skills add TurboDocx/quickstart.

Prefer to drag and drop? Use signer roles

Anchors are great when you generate the PDF yourself. They're annoying when the contract comes from a lawyer, changes often, or you just want to place the fields by hand. For that case, set the document up once in the TurboDocx app and send it from code by role.

One-time setup in the app:

  1. Upload your PDF as a template
  2. On its Signatures tab, add each signer as a role (for example "Client" and "Countersigner")
  3. For a role that changes every time, tick Fill in name and email when sending. For a role that's always the same person (your countersigner), give it a saved name and email
  4. Drag each role's fields onto the PDF and save the signature setup

Then from code, name each recipient's role. The fields you placed come along with the role, so you never pass coordinates or anchors. Role keys are listed under Use via API on the template's page.

This fits best when the same document goes out again and again with different signers. The quickstart skill already prefers this path for any recurring document: it tells you what to click in TurboDocx, then generates the role-based send code and a helper that lists the template's roles.

Node.js / TypeScript
import { TurboSign } from '@turbodocx/sdk'; // TurboSign.configure({ ... }) as in Step 1 const templateId 

原文:https://www.a8q9.cn/en/article/e-signature-api-tutorial-sign-pdfs-in-node-js-python-php-go-java-ruby-8d24d2.html

相关文章