Integrating SignNow electronic signature on a website
SignNow is a competitor to DocuSign from airSlate, focused on SMB and API integrations. It differs with simpler pricing and REST API without mandatory SDK — all operations via standard HTTP requests. Documentation: docs.signnow.com.
Authentication
SignNow uses OAuth 2.0 with client_credentials for server integrations:
class SignNowAuthService
{
private string $baseUrl = 'https://api.signnow.com';
public function getToken(): string
{
$response = Http::withBasicAuth(
config('services.signnow.client_id'),
config('services.signnow.client_secret')
)->asForm()->post("{$this->baseUrl}/oauth2/token", [
'grant_type' => 'client_credentials',
'scope' => '*',
]);
$token = $response->json('access_token');
// Cache — token lives for one hour
Cache::put('signnow_token', $token, now()->addMinutes(55));
return $token;
}
public function token(): string
{
return Cache::get('signnow_token') ?? $this->getToken();
}
}
For flows with a specific user's signature (user-level operations), Password Grant with SignNow user credentials is needed. For most API integrations, client_credentials is sufficient.
Document upload and field placement
class SignNowDocumentService
{
public function __construct(private SignNowAuthService $auth)
{}
public function uploadDocument(string $pdfPath, string $fileName): string
{
$response = Http::withToken($this->auth->token())
->attach('file', file_get_contents($pdfPath), $fileName)
->post('https://api.signnow.com/document');
return $response->json('id'); // document ID
}
public function addSignatureFields(string $documentId, array $signers): void
{
$fields = [];
$roleIndex = 0;
foreach ($signers as $signer) {
$fields[] = [
'type' => 'signature',
'role' => $signer['role'] ?? "Signer {$roleIndex}",
'role_id' => (string)$roleIndex,
'required' => true,
'height' => 40,
'width' => 200,
'x' => 100,
'y' => 600,
'page_number' => 0,
];
$roleIndex++;
}
Http::withToken($this->auth->token())
->put("https://api.signnow.com/document/{$documentId}", [
'fields' => $fields,
]);
}
}
SignNow supports smart fields via anchor-tags in PDF: if the document contains text like [[sig|req|signer1]], SignNow automatically places a signature field. This is more convenient than fixed coordinates.
Creating invite for signers
public function sendInvite(string $documentId, array $signers): string
{
$recipients = [];
foreach ($signers as $index => $signer) {
$recipients[] = [
'email' => $signer['email'],
'role' => $signer['role'] ?? "Signer {$index}",
'role_id' => (string)$index,
'order' => $index + 1,
'reminder' => [
'remind_before' => 0,
'remind_after' => 3, // remind after 3 days
'remind_repeat' => 2, // repeat every 2 days
],
'expiration_days' => 30,
'subject' => 'Please sign the document',
'message' => "Hello, {$signer['name']}! Please sign the attached document.",
];
}
$response = Http::withToken($this->auth->token())
->post("https://api.signnow.com/document/{$documentId}/invite", [
'to' => $recipients,
'from' => config('services.signnow.sender_email'),
]);
return $response->json('status'); // 'success'
}
Embedded signing
For signing without redirecting to SignNow — get a link to embed in iframe or redirect:
public function getSigningLink(string $documentId, string $email): string
{
$response = Http::withToken($this->auth->token())
->post("https://api.signnow.com/link", [
'document_id' => $documentId,
]);
// link is valid once and for limited time
return $response->json('url');
}
Embedded signing works via <iframe src="..."> or window.open. After signing, SignNow calls postMessage or redirects to the specified URL.
Webhook: document events
// Register webhook
Http::withToken($this->auth->token())
->post('https://api.signnow.com/api/v2/events', [
'event' => 'document.complete',
'entity_id' => $documentId,
'action' => 'callback',
'callback_url' => route('webhooks.signnow'),
]);
// Handler
public function handleSignNowWebhook(Request $request): Response
{
$data = $request->json()->all();
if (($data['event'] ?? '') === 'document.complete') {
$documentId = $data['meta']['document_id'];
DownloadSignedDocumentJob::dispatch($documentId);
}
return response()->noContent();
}
Downloading the signed document
public function downloadSigned(string $documentId): string
{
$response = Http::withToken($this->auth->token())
->get("https://api.signnow.com/document/{$documentId}/download", [
'type' => 'collapsed', // single PDF with all signatures
]);
$path = storage_path("app/contracts/signed_{$documentId}.pdf");
file_put_contents($path, $response->body());
return $path;
}
Differences from DocuSign
SignNow works via pure REST without mandatory SDK, simplifying integration in any language. No built-in fiscal document storage — if you need to store signed contracts with audit trail, this is organized on the application side. Embedded signing functionality is less flexible for UI customization. However, the API is more straightforward and well-documented.
Timeline
OAuth, document upload, sending invite, webhook, and downloading signed file: 2–3 working days. With embedded signing and full UX flow inside the website: 3–4 working days.







