Getting Started
Send your first envelope for signing using the SignSecure Sign Pad API.
This guide walks you through the full signing flow -- from creating an envelope to downloading the signed PDF -- with examples in multiple languages.
Prefer an API client? Download the Postman collection, import it, and set the apiKey collection variable. Its baseUrl defaults to the production API.
1. Create an API Key
Go to Settings > API Keys in the dashboard and create a new key. Copy it immediately -- it won't be shown again.
Every key has complete API permissions inside the access scope you choose: All resources, Personal only, or One workspace. The scope is configured when the key is created; no additional workspace header is required.
Set your key as an environment variable:
export API_KEY="signsecure_your_key_here"$env:API_KEY = "signsecure_your_key_here"set API_KEY=signsecure_your_key_here2. Create an Envelope with Recipients
Create an envelope, add recipients, and get a presigned upload URL -- all in a single request. The recipients and workflow fields are optional here but save you extra API calls.
curl -X POST https://api.signpad.signsecure.in/api/v1/envelopes \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fileName": "contract.pdf",
"title": "Service Agreement",
"recipients": [
{
"name": "Jane Doe",
"email": "jane@example.com",
"role": "signer",
"order": 1,
"signatureMethod": "electronic"
},
{
"name": "Bob Smith",
"email": "bob@example.com",
"role": "approver",
"order": 2
}
],
"workflow": {
"mode": "sequential",
"verificationMethod": "email_verification",
"message": "Please review and sign this agreement.",
"emailNotifications": "all"
}
}'const API_KEY = process.env.API_KEY;
const BASE_URL = "https://api.signpad.signsecure.in/api/v1";
const response = await fetch(`${BASE_URL}/envelopes`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
fileName: "contract.pdf",
title: "Service Agreement",
recipients: [
{
name: "Jane Doe",
email: "jane@example.com",
role: "signer",
order: 1,
signatureMethod: "electronic",
},
{
name: "Bob Smith",
email: "bob@example.com",
role: "approver",
order: 2,
},
],
workflow: {
mode: "sequential",
verificationMethod: "email_verification",
message: "Please review and sign this agreement.",
emailNotifications: "all",
},
}),
});
const envelope = await response.json();
console.log(envelope.id); // "env_abc123"
console.log(envelope.upload.url); // S3 upload URL
console.log(envelope.recipientsAdded); // 2import os
import requests
API_KEY = os.environ["API_KEY"]
BASE_URL = "https://api.signpad.signsecure.in/api/v1"
response = requests.post(
f"{BASE_URL}/envelopes",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
},
json={
"fileName": "contract.pdf",
"title": "Service Agreement",
"recipients": [
{
"name": "Jane Doe",
"email": "jane@example.com",
"role": "signer",
"order": 1,
"signatureMethod": "electronic",
},
{
"name": "Bob Smith",
"email": "bob@example.com",
"role": "approver",
"order": 2,
},
],
"workflow": {
"mode": "sequential",
"verificationMethod": "email_verification",
"message": "Please review and sign this agreement.",
"emailNotifications": "all",
},
},
)
envelope = response.json()
print(envelope["id"]) # "env_abc123"
print(envelope["upload"]["url"]) # S3 upload URL
print(envelope["recipientsAdded"]) # 2$apiKey = getenv('API_KEY');
$baseUrl = 'https://api.signpad.signsecure.in/api/v1';
$ch = curl_init("$baseUrl/envelopes");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer $apiKey",
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'fileName' => 'contract.pdf',
'title' => 'Service Agreement',
'recipients' => [
[
'name' => 'Jane Doe',
'email' => 'jane@example.com',
'role' => 'signer',
'order' => 1,
'signatureMethod' => 'electronic',
],
[
'name' => 'Bob Smith',
'email' => 'bob@example.com',
'role' => 'approver',
'order' => 2,
],
],
'workflow' => [
'mode' => 'sequential',
'verificationMethod' => 'email_verification',
'message' => 'Please review and sign this agreement.',
'emailNotifications' => 'all',
],
]),
]);
$response = curl_exec($ch);
curl_close($ch);
$envelope = json_decode($response, true);
echo $envelope['id']; // "env_abc123"
echo $envelope['recipientsAdded']; // 2package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
apiKey := os.Getenv("API_KEY")
baseURL := "https://api.signpad.signsecure.in/api/v1"
body, _ := json.Marshal(map[string]interface{}{
"fileName": "contract.pdf",
"title": "Service Agreement",
"recipients": []map[string]interface{}{
{
"name": "Jane Doe", "email": "jane@example.com",
"role": "signer", "order": 1, "signatureMethod": "electronic",
},
{
"name": "Bob Smith", "email": "bob@example.com",
"role": "approver", "order": 2,
},
},
"workflow": map[string]interface{}{
"mode": "sequential",
"verificationMethod": "email_verification",
"message": "Please review and sign this agreement.",
"emailNotifications": "all",
},
})
req, _ := http.NewRequest("POST", baseURL+"/envelopes", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var envelope map[string]interface{}
json.NewDecoder(resp.Body).Decode(&envelope)
fmt.Println(envelope["id"]) // "env_abc123"
fmt.Println(envelope["recipientsAdded"]) // 2
}Response:
{
"id": "env_abc123",
"title": "Service Agreement",
"fileName": "contract.pdf",
"status": "draft",
"fileKey": "uploads/env_abc123/contract.pdf",
"upload": {
"url": "https://s3.amazonaws.com/...",
"fields": { "key": "...", "policy": "...", "signature": "..." }
},
"expiresIn": 3600,
"maxFileSize": 10485760,
"recipientsAdded": 2,
"createdAt": "2026-03-11T10:00:00.000Z"
}Recipient Roles
| Role | Description |
|---|---|
signer | Must sign the envelope |
approver | Must approve (no signature required) |
cc | Receives a copy, no action needed |
Each recipient needs a name plus at least one contact: an email, a phone in E.164 format (e.g. +919876543210, used for WhatsApp delivery), or both.
Signature Methods
All methods are billed per signer at your account's pricing-tier rates -- see Get Credit Balance.
| Method | Description |
|---|---|
electronic | Draw or type a signature (10 credits per signer, default tier) |
aadhaar_otp | Aadhaar eSign with OTP verification (20 credits per signer, default tier) |
dsc_usb | Digital Signature Certificate via USB token (10 credits per signer, default tier) |
all | Recipient chooses at signing time |
Workflow Options
| Option | Values | Description |
|---|---|---|
mode | sequential, parallel | Sequential = recipients sign in order. Parallel = all sign at once |
verificationMethod | link_only, email_verification, whatsapp_verification, email_or_whatsapp_verification | Require an invite link, a code through one channel, or let the recipient choose email or WhatsApp. |
emailNotifications | all, completion_only, none | Controls when recipients get email notifications |
whatsappNotifications | all, completion_only, none | Controls when recipients with a phone get WhatsApp notifications. Defaults to none |
redirectUrl | HTTPS URL | Optional. Redirects signers back to your app after signing. Query params envelopeId, signerEmail, role, status, and event are appended automatically |
3. Upload the PDF
Use the presigned upload URL from the previous response to upload your file. Only application/pdf files up to 10 MB are supported.
curl -X PUT "UPLOAD_URL_FROM_RESPONSE" \
-H "Content-Type: application/pdf" \
--data-binary @contract.pdfimport { readFileSync } from "node:fs";
const fileBuffer = readFileSync("contract.pdf");
await fetch(envelope.upload.url, {
method: "PUT",
headers: { "Content-Type": "application/pdf" },
body: fileBuffer,
});with open("contract.pdf", "rb") as f:
requests.put(
envelope["upload"]["url"],
headers={"Content-Type": "application/pdf"},
data=f.read(),
)$fileData = file_get_contents('contract.pdf');
$ch = curl_init($envelope['upload']['url']);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/pdf'],
CURLOPT_POSTFIELDS => $fileData,
]);
curl_exec($ch);
curl_close($ch);fileData, _ := os.ReadFile("contract.pdf")
req, _ := http.NewRequest("PUT", uploadUrl, bytes.NewReader(fileData))
req.Header.Set("Content-Type", "application/pdf")
http.DefaultClient.Do(req)4. Send for Signing
Since we already included the workflow config in the create call, we just send. Workflow settings (mode, verification, email notifications, redirect URL, etc.) are always configured during envelope creation.
curl -X POST https://api.signpad.signsecure.in/api/v1/envelopes/env_abc123/send \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{}'const sendResult = await fetch(
`${BASE_URL}/envelopes/${envelope.id}/send`,
{
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
}
).then((r) => r.json());response = requests.post(
f"{BASE_URL}/envelopes/{envelope['id']}/send",
headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"},
json={},
)
result = response.json()$ch = curl_init("$baseUrl/envelopes/{$envelope['id']}/send");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer $apiKey",
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([]),
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);req, _ := http.NewRequest("POST",
baseURL+"/envelopes/"+envelopeID+"/send",
bytes.NewBufferString("{}"))
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()The send endpoint accepts no request body. All workflow configuration (mode, verification method, email notifications, redirect URL, message, etc.) must be set during envelope creation via POST /envelopes. See the Complete Workflow guide for the step-by-step approach.
5. Check Progress
Poll the signing status to track who has signed.
curl https://api.signpad.signsecure.in/api/v1/envelopes/env_abc123/status \
-H "Authorization: Bearer $API_KEY"const status = await fetch(
`${BASE_URL}/envelopes/${envelope.id}/status`,
{ headers: { Authorization: `Bearer ${API_KEY}` } }
).then((r) => r.json());
console.log(`${status.completedCount}/${status.totalRecipients} signed`);
console.log(`Status: ${status.status}`); // "pending" or "completed"response = requests.get(
f"{BASE_URL}/envelopes/{envelope['id']}/status",
headers={"Authorization": f"Bearer {API_KEY}"},
)
status = response.json()
print(f"{status['completedCount']}/{status['totalRecipients']} signed")
print(f"Status: {status['status']}") # "pending" or "completed"$ch = curl_init("$baseUrl/envelopes/{$envelope['id']}/status");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $apiKey"],
]);
$status = json_decode(curl_exec($ch), true);
curl_close($ch);
echo "{$status['completedCount']}/{$status['totalRecipients']} signed";req, _ := http.NewRequest("GET",
baseURL+"/envelopes/"+envelopeID+"/status", nil)
req.Header.Set("Authorization", "Bearer "+apiKey)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var status map[string]interface{}
json.NewDecoder(resp.Body).Decode(&status)
fmt.Printf("%.0f/%.0f signed\n", status["completedCount"], status["totalRecipients"])Response:
{
"envelopeId": "env_abc123",
"status": "pending",
"totalRecipients": 2,
"completedCount": 1,
"percentage": 50,
"recipients": [
{
"name": "Jane Doe",
"email": "jane@example.com",
"status": "signed",
"signedAt": "2026-03-11T11:20:00.000Z",
"order": 1
},
{
"name": "Bob Smith",
"email": "bob@example.com",
"status": "pending",
"order": 2
}
]
}Instead of polling, set up Webhooks to get notified in real-time when recipients sign.
6. Download the Signed PDF
Once the envelope status is completed, get a download URL for the signed PDF.
curl https://api.signpad.signsecure.in/api/v1/envelopes/env_abc123/file \
-H "Authorization: Bearer $API_KEY"const download = await fetch(
`${BASE_URL}/envelopes/${envelope.id}/file`,
{ headers: { Authorization: `Bearer ${API_KEY}` } }
).then((r) => r.json());
console.log(download.url); // temporary S3 URL, valid for 1 hourresponse = requests.get(
f"{BASE_URL}/envelopes/{envelope['id']}/file",
headers={"Authorization": f"Bearer {API_KEY}"},
)
download = response.json()
print(download["url"]) # temporary S3 URL, valid for 1 hour$ch = curl_init("$baseUrl/envelopes/{$envelope['id']}/file");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $apiKey"],
]);
$download = json_decode(curl_exec($ch), true);
curl_close($ch);
echo $download['url']; // temporary S3 URL, valid for 1 hourreq, _ := http.NewRequest("GET",
baseURL+"/envelopes/"+envelopeID+"/file", nil)
req.Header.Set("Authorization", "Bearer "+apiKey)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var download map[string]interface{}
json.NewDecoder(resp.Body).Decode(&download)
fmt.Println(download["url"]) // temporary S3 URL, valid for 1 hourResponse:
{
"url": "https://s3.amazonaws.com/...",
"expiresIn": 3600,
"fileName": "contract.pdf"
}Next Steps
- Set up Webhooks to get notified in real-time instead of polling
- Use Templates to reuse envelope layouts with predefined recipients
- Check your Credits balance and usage
- Read the Complete Workflow guide for every API action with examples