SignSecure Sign PadSignSecure Sign Pad

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_here

2. 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); // 2
import 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']; // 2
package 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

RoleDescription
signerMust sign the envelope
approverMust approve (no signature required)
ccReceives 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.

MethodDescription
electronicDraw or type a signature (10 credits per signer, default tier)
aadhaar_otpAadhaar eSign with OTP verification (20 credits per signer, default tier)
dsc_usbDigital Signature Certificate via USB token (10 credits per signer, default tier)
allRecipient chooses at signing time

Workflow Options

OptionValuesDescription
modesequential, parallelSequential = recipients sign in order. Parallel = all sign at once
verificationMethodlink_only, email_verification, whatsapp_verification, email_or_whatsapp_verificationRequire an invite link, a code through one channel, or let the recipient choose email or WhatsApp.
emailNotificationsall, completion_only, noneControls when recipients get email notifications
whatsappNotificationsall, completion_only, noneControls when recipients with a phone get WhatsApp notifications. Defaults to none
redirectUrlHTTPS URLOptional. 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.pdf
import { 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 hour
response = 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 hour
req, _ := 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 hour

Response:

{
  "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

On this page