How to Implement Embedded Signing
This guide demonstrates how to create envelopes with embedded signing and generate iframe URLs for seamless integration into your application. You'll learn how to create a single signer envelope that delivers via embed and how to retrieve the embedded signing URL.
Overview
Embedded signing allows you to integrate the signing experience directly into your application without redirecting users to external pages:
- Create an envelope with
deliver_via: "embed"for the signer - Retrieve the embedded URL using the packet ID
- Embed the signing interface in your application using an iframe
Envelopes are automatically sent in the background after creation, though they initially show pending status while processing.
Prerequisites
Before you begin, ensure you have:
- A Blueink account with API access
- Your API key (available in the Blueink Dashboard under Apps)
- A PDF file or document template for signing
- Basic knowledge of HTTP requests and your chosen programming language
- Understanding of iframe integration in web applications
If you don't have an API key yet, visit the Authentication guide to learn how to obtain one.
When using is_test: true, envelopes can only be sent to email addresses that belong to users in your Blueink account. Make sure the signer email addresses match existing users in your account when testing.
Step 1: Prepare Your Environment
Authentication Setup
All requests to the Blueink API require authentication using your API key in the Authorization header:
Authorization: Token YOUR_API_KEY_HERE
Required Information
For this guide, you'll need:
- PDF file path: The local path to your PDF file (or template ID)
- Signer information: Name and email address of the person who will sign
- Embed configuration: Setting up the packet for embedded delivery
Step 2: Create Envelope with Embedded Signing
Create an envelope with a single signer configured for embedded delivery using deliver_via: "embed".
- Python
- JavaScript (Node.js)
import requests
import json
# Configuration
API_KEY = "your_api_key_here"
BASE_URL = "https://api.blueink.com/api/v2"
PDF_FILE_PATH = "path/to/your/document.pdf"
# Headers for authentication
headers = {
"Authorization": f"Token {API_KEY}"
}
# Bundle request data for embedded signing
bundle_request = {
"label": "Embedded Signature Request",
"is_test": True, # Set to False for production
# "status": "dr", # Uncomment to create as draft (default is pending)
"packets": [
{
"key": "signer-1",
"name": "John Doe",
"email": "[email protected]",
"deliver_via": "embed" # This enables embedded signing
}
],
"documents": [
{
"key": "contract-doc",
"file_index": "0", # References the uploaded file
"filename": "contract.pdf"
}
]
}
# Prepare multipart form data
try:
with open(PDF_FILE_PATH, 'rb') as pdf_file:
files = {
'files[0]': ('contract.pdf', pdf_file, 'application/pdf')
}
data = {
'bundle_request': json.dumps(bundle_request)
}
# Create the envelope with embedded signing
response = requests.post(
f"{BASE_URL}/bundles/",
headers=headers,
files=files,
data=data
)
if response.status_code == 201:
bundle = response.json()
print(f"✅ Envelope created successfully!")
print(f"Bundle ID: {bundle['id']}")
print(f"Status: {bundle['status']}")
print(f"📧 Envelope will be automatically sent to signers by background processes.")
print(f"Packet ID: {bundle['packets'][0]['id']}") # We'll need this for the embed URL
# Store the packet ID for the next step
packet_id = bundle['packets'][0]['id']
else:
print(f"❌ Error creating envelope: {response.status_code}")
print(f"Response: {response.text}")
except Exception as e:
print(f"❌ Error: {e}")
const fs = require('fs');
const FormData = require('form-data');
const axios = require('axios');
// Configuration
const API_KEY = 'your_api_key_here';
const BASE_URL = 'https://api.blueink.com/api/v2';
const PDF_FILE_PATH = 'path/to/your/document.pdf';
// Bundle request data for embedded signing
const bundleRequest = {
label: 'Embedded Signature Request',
is_test: true, // Set to false for production
// status: 'dr', // Uncomment to create as draft (default is pending)
packets: [
{
key: 'signer-1',
name: 'John Doe',
email: '[email protected]',
deliver_via: 'embed' // This enables embedded signing
}
],
documents: [
{
key: 'contract-doc',
file_index: '0', // References the uploaded file
filename: 'contract.pdf'
}
]
};
async function createEmbeddedEnvelope() {
try {
// Prepare form data
const form = new FormData();
form.append('files[0]', fs.createReadStream(PDF_FILE_PATH), 'contract.pdf');
form.append('bundle_request', JSON.stringify(bundleRequest));
const response = await axios.post(
`${BASE_URL}/bundles/`,
form,
{
headers: {
'Authorization': `Token ${API_KEY}`,
...form.getHeaders()
}
}
);
if (response.status === 201) {
const bundle = response.data;
console.log('✅ Envelope created successfully!');
console.log(`Bundle ID: ${bundle.id}`);
console.log(`Status: ${bundle.status}`);
console.log('📧 Envelope will be automatically sent to signers by background processes.');
console.log(`Packet ID: ${bundle.packets[0].id}`); // We'll need this for the embed URL
// Store the packet ID for the next step
const packetId = bundle.packets[0].id;
return { bundle, packetId };
}
} catch (error) {
if (error.response) {
console.error(`❌ Error creating envelope: ${error.response.status}`);
console.error(`Response: ${JSON.stringify(error.response.data, null, 2)}`);
} else {
console.error('❌ Error:', error.message);
}
throw error;
}
}
// Execute the function
createEmbeddedEnvelope()
.then(result => {
console.log('Embedded envelope creation completed');
// Continue to Step 3 with the packet ID
})
.catch(error => {
console.error('Failed to create embedded envelope');
});
Step 3: Retrieve the Embedded Signing URL
Once the envelope is created, use the packet ID to retrieve the embedded signing URL that can be used in an iframe.
- Python
- JavaScript (Node.js)
def get_embedded_signing_url(packet_id):
"""Retrieve the embedded signing URL for a packet"""
try:
response = requests.post(
f"{BASE_URL}/packets/{packet_id}/embed_url/",
headers=headers
)
if response.status_code == 200:
embed_data = response.json()
embed_url = embed_data['url']
print(f"✅ Embedded URL retrieved successfully!")
print(f"Embed URL: {embed_url}")
return embed_url
else:
print(f"❌ Error retrieving embed URL: {response.status_code}")
print(f"Response: {response.text}")
return None
except Exception as e:
print(f"❌ Error retrieving embed URL: {e}")
return None
# Usage (continuing from Step 2)
if 'packet_id' in locals():
embed_url = get_embedded_signing_url(packet_id)
if embed_url:
print(f"\n🎯 Integration Instructions:")
print(f"Use this URL in an iframe:")
print(f'<iframe src="{embed_url}" width="100%" height="600px" frameborder="0"></iframe>')
async function getEmbeddedSigningUrl(packetId) {
try {
const response = await axios.post(
`${BASE_URL}/packets/${packetId}/embed_url/`,
{},
{
headers: {
'Authorization': `Token ${API_KEY}`,
'Content-Type': 'application/json'
}
}
);
if (response.status === 200) {
const embedData = response.data;
const embedUrl = embedData.url;
console.log('✅ Embedded URL retrieved successfully!');
console.log(`Embed URL: ${embedUrl}`);
return embedUrl;
}
} catch (error) {
if (error.response) {
console.error(`❌ Error retrieving embed URL: ${error.response.status}`);
console.error(`Response: ${JSON.stringify(error.response.data, null, 2)}`);
} else {
console.error('❌ Error retrieving embed URL:', error.message);
}
throw error;
}
}
// Complete workflow function
async function createAndGetEmbedUrl() {
try {
// Step 1: Create embedded envelope
const result = await createEmbeddedEnvelope();
const { bundle, packetId } = result;
// Step 2: Get embedded signing URL
const embedUrl = await getEmbeddedSigningUrl(packetId);
if (embedUrl) {
console.log('\n🎯 Integration Instructions:');
console.log('Use this URL in an iframe:');
console.log(`<iframe src="${embedUrl}" width="100%" height="600px" frameborder="0"></iframe>`);
}
return { bundle, embedUrl };
} catch (error) {
console.error('Failed to complete embedded signing workflow:', error.message);
}
}
// Execute the complete workflow
createAndGetEmbedUrl();
API Response Examples
Envelope Creation Response
When successful, the envelope creation returns a 201 Created status with the envelope details:
{
"id": "lL7g5Hn2Ni",
"created": "2025-09-04T23:18:57.615334Z",
"sent": null,
"completed_at": null,
"label": "Embedded Signature Request",
"in_order": false,
"is_test": true,
"status": "pe",
"custom_key": null,
"custom_text": "",
"packets": [
{
"id": "a6v1qN4z8T",
"key": "signer-1",
"name": "John Doe",
"email": "[email protected]",
"phone": "",
"auth_sms": false,
"auth_selfie": false,
"auth_secrets": null,
"auth_id": false,
"person_id": "db600721-d8e7-42d3-8ebe-a180b4864eb8",
"status": "ne",
"deliver_via": "embed",
"completed_at": null,
"last_accessed_at": null,
"order": 1,
"signing_complete_redirect": "",
"suppress_all": false,
"suppress_docs_ready": false,
"suppress_signing": false,
"suppress_reminder": false
}
],
"documents": [
{
"id": "doc456",
"key": "contract-doc",
"filename": "contract.pdf",
"status": "pr"
}
],
"errors": null,
"cc_emails": [],
"email_message": "",
"email_subject": "",
"team": null,
"tags": [],
"send_reminders": false,
"reminder_offset": 0,
"reminder_interval": 0,
"reminder_expires": 0,
"expires": null,
"sms_message": "",
"requester_name": "",
"requester_email": "",
"payment": null
}
Embedded URL Response
The embed URL endpoint returns a 200 OK status with the signing URL:
{
"url": "https://app.blueink.com/packets/a6v1qN4z8T/embed/?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
Frontend Integration
HTML iframe Integration
Once you have the embedded URL, integrate it into your web application:
<!DOCTYPE html>
<html>
<head>
<title>Document Signing</title>
<style>
.signing-container {
width: 100%;
height: 600px;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
}
.signing-iframe {
width: 100%;
height: 100%;
border: none;
}
</style>
</head>
<body>
<div class="signing-container">
<iframe
id="signing-iframe"
class="signing-iframe"
src="EMBED_URL_HERE"
title="Document Signing">
</iframe>
</div>
<script>
// Optional: Listen for signing completion events
window.addEventListener('message', function(event) {
if (event.origin !== 'https://app.blueink.com') {
return;
}
if (event.data.type === 'signing_complete') {
console.log('Document signed successfully!');
// Handle signing completion
window.location.href = '/success';
}
});
</script>
</body>
</html>
React Integration Example
import React, { useEffect, useState } from 'react';
const EmbeddedSigning = ({ packetId }) => {
const [embedUrl, setEmbedUrl] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const getEmbedUrl = async () => {
try {
const response = await fetch(`/api/packets/${packetId}/embed_url/`, {
method: 'POST',
headers: {
'Authorization': `Token ${process.env.REACT_APP_API_KEY}`,
'Content-Type': 'application/json'
}
});
if (response.ok) {
const data = await response.json();
setEmbedUrl(data.url);
} else {
setError('Failed to load signing interface');
}
} catch (err) {
setError('Network error occurred');
} finally {
setLoading(false);
}
};
if (packetId) {
getEmbedUrl();
}
}, [packetId]);
const handleMessage = (event) => {
if (event.origin !== 'https://app.blueink.com') {
return;
}
if (event.data.type === 'signing_complete') {
console.log('Document signed successfully!');
// Handle signing completion
}
};
useEffect(() => {
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, []);
if (loading) {
return <div>Loading signing interface...</div>;
}
if (error) {
return <div>Error: {error}</div>;
}
return (
<div style={{ width: '100%', height: '600px', border: '1px solid #ddd' }}>
<iframe
src={embedUrl}
width="100%"
height="100%"
frameBorder="0"
title="Document Signing"
/>
</div>
);
};
export default EmbeddedSigning;
Error Handling and Best Practices
Common Error Scenarios
-
Packet Not Found (404 Not Found)
{
"detail": "Packet not found."
} -
Authentication Error (401 Unauthorized)
{
"detail": "Invalid token."
} -
Packet Not Ready for Embedding (400 Bad Request)
{
"detail": "Packet is not configured for embedded delivery."
}
Best Practices
-
Security Considerations
- Never expose your API key in frontend code
- Use server-side endpoints to generate embed URLs
- Implement proper CORS policies
- Validate packet ownership before generating URLs
-
User Experience
- Provide loading states while generating embed URLs
- Handle iframe loading errors gracefully
- Implement responsive iframe sizing
- Listen for completion events to redirect users
-
Error Handling
- Always check response status codes
- Implement retry logic for transient failures
- Provide meaningful error messages to users
- Log errors for debugging purposes
-
Performance
- Cache embed URLs when appropriate (they have limited lifetime)
- Preload signing interfaces when possible
- Optimize iframe dimensions for your layout
Security and Compliance
iframe Security
- Content Security Policy: Configure CSP headers to allow Blueink domains
- X-Frame-Options: Ensure your application allows iframe embedding
- HTTPS Required: Embedded signing requires HTTPS for security
Data Protection
- Embedded signing maintains the same security standards as regular signing
- All data transmission is encrypted
- Audit trails are preserved for embedded signatures
- Compliance certifications apply to embedded workflows
Next Steps
Now that you've learned how to implement embedded signing, you might want to explore:
- Send Envelope with Uploaded PDF - How to create envelopes by uploading PDF files
- Send Envelope with Templates - How to use templates for embedded signing
- API Reference - Complete API documentation for all endpoints and operations
- Webhook Integration - How to receive real-time notifications about signing events
Need Help?
If you encounter issues or have questions:
- Check the API Reference for detailed endpoint documentation
- Visit our Support Center for additional resources
- Contact our support team through the Blueink Dashboard
Embedded signing provides a seamless user experience by keeping users within your application throughout the signing process. By following this guide, you can integrate professional e-signature capabilities directly into your workflow.