Overview

This guide covers the three most common integration scenarios with Formify:

  1. Send a document for signing — Upload a PDF and create a signature request
  2. Get notified when signing status changes — Use webhooks to trigger automated workflows
  3. Download the signed document — Retrieve the completed PDF
Prerequisites: Before starting, ensure you have completed OAuth 2.0 authentication and have a valid access_token. See the OAuth 2.0 section in the API reference.

Flow 1: Send a Document for Signing

This is the most common workflow: upload a PDF and send it to one or more signees.

1
Upload PDF file
POST /files with multipart/form-data
2
Receive fileId
Save this ID for the next step
3
Create signature request
POST /docs with fileId and signee details
4
Signees receive email or SMS
Formify sends signing invitations automatically

Step 1: Upload PDF

POST https://docs-api.formify.se/v1/files
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: multipart/form-data

file: [PDF binary data]
errorLanguage: sv

Response:

{
  "fileId": "d6fb0d8c-4bd4-4ef6-b5f3-8a25570d7abc",
  "name": "Contract.pdf",
  "uploadedAt": "2024-03-17T10:12:34Z"
}
File requirements:
  • Maximum size: 50 MB
  • Format: PDF only
  • Must not be password-protected
  • Must not contain existing digital signatures

Step 2: Create Signature Request

Use the fileId from step 1 to create a signature request. The simplest approach is to use signaturePlacement: "new_page", which adds signatures on a new page at the end of the document.

POST https://docs-api.formify.se/v1/docs
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json

{
  "fileId": "d6fb0d8c-4bd4-4ef6-b5f3-8a25570d7abc",
  "name": "Contract - Project Alpha",
  "language": "sv",
  "signeeDetails": [
    {
      "fullName": "Anna Andersson",
      "emailAddress": "anna@example.com",
      "signatureType": "bankid_identification",
      "signaturePlacement": "new_page"
    }
  ],
  "personalMessage": "Please sign this contract."
}

Response:

{
  "documentId": "2a591ba0-7991-4ce0-9f02-2b76c18b8c86",
  "name": "Contract - Project Alpha",
  "createdAt": "2024-03-17T10:13:05Z",
  "documentUrl": "https://app.formify.se/#/docs/2a591ba0-7991-4ce0-9f02-2b76c18b8c86"
}
Save the documentId! You will need it to track document status and retrieve the signed file later.

Multiple Signees

To add multiple signees, include multiple objects in the signeeDetails array:

"signeeDetails": [
  {
    "fullName": "Anna Andersson",
    "emailAddress": "anna@example.com",
    "signatureType": "bankid_identification",
    "signaturePlacement": "new_page",
    "signingOrder": 1
  },
  {
    "fullName": "Erik Eriksson",
    "emailAddress": "erik@example.com",
    "signatureType": "bankid_identification",
    "signaturePlacement": "new_page",
    "signingOrder": 2
  }
]
Signing order: Set enableSigningOrder: true in the document request and use different signingOrder values to enforce sequential signing. Signees with the same order value can sign simultaneously.

Flow 2: Get Notified When Document Status Changes

Use webhooks to receive real-time notifications when documents are signed or completed.

Step 1: Register Webhook

Register your webhook URL once per customer account after OAuth activation:

POST https://docs-api.formify.se/v1/webhooks
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json

{
  "webhookUrl": "https://your-app.com/webhooks/formify",
  "name": "Formify Document Webhook",
  "eventTypes": ["document.completed"]
}

Response:

{
  "webhookId": "3d0cb5c4-6f51-4e56-8b7c-0f9d0d5a1234",
  "webhookUrl": "https://your-app.com/webhooks/formify",
  "name": "Formify Document Webhook",
  "eventTypes": ["document.completed"],
  "signingSecret": "whsec_xxxxxxxxxxxxxxxxx"
}
Important: The event type is document.completed. You can also subscribe to events such as document.signed and document.created.
Save the signing secret: The response includes a signingSecret for this webhook. Store it securely when the webhook is created — you will need it to verify the X-Formify-Signature header on every incoming webhook delivery.

Secret management: Each webhook has its own signing secret. If the secret is compromised or lost, rotate it using POST /webhooks/{webhookId}/rotate-secret. The response contains the new signingSecret. Update your webhook verification logic with the new secret immediately after rotating.

Step 2: Receive Webhook Events

When a subscribed event occurs, Formify sends a POST request to your webhook URL.

Headers included in every delivery:

Example payload:

{
  "event": {
    "eventId": "f3bcd5f8-45de-4b90-8b8b-9ce0c8e9b8e2",
    "type": "document.completed",
    "date": "2024-03-17T10:20:00Z"
  },
  "data": {
    "documentId": "2a591ba0-7991-4ce0-9f02-2b76c18b8c86",
    "accountId": "...",
    "userId": "...",
    "signeeId": null
  }
}

Step 3: Verify and Handle the Webhook

Your webhook endpoint should:

  1. Verify the HMAC signature — Validate X-Formify-Signature using your webhook signing secret and the raw request body
  2. Check that the timestamp is recent — Validate X-Formify-Timestamp to reduce replay attacks
  3. Make processing idempotent — Store processed eventId values to prevent duplicate processing
  4. Look up the document — Use data.documentId to find the corresponding record in your system
  5. Trigger your workflow — Update status, send notifications, download the PDF, and so on
  6. Respond quickly — Return HTTP 200 as soon as possible
const crypto = require('crypto');
const express = require('express');

const app = express();

app.post('/webhooks/formify', express.raw({ type: 'application/json' }), async (req, res) => {
  const timestamp = req.headers['x-formify-timestamp'];
  const signature = req.headers['x-formify-signature'];
  const secret = 'whsec_...'; // your webhook signing secret

  const signedPayload = `${timestamp}.${req.body}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  const isValid = crypto.timingSafeEqual(
    Buffer.from(expectedSignature),
    Buffer.from(signature)
  );

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  const payload = JSON.parse(req.body);
  const { event, data } = payload;

  const alreadyProcessed = await db.checkEventId(event.eventId);
  if (alreadyProcessed) {
    return res.status(200).send('OK');
  }

  await db.storeEventId(event.eventId);

  if (event.type === 'document.completed') {
    await handleDocumentCompleted(data.documentId);
  }

  res.status(200).send('OK');
});
Important: Use the raw request body exactly as received when verifying the signature. Do not verify against a re-serialized JSON payload.

Flow 3: Download Signed Document

Once a document is completed, you can either show a link to Formify or download the signed PDF to your own system.

Option A: Show Link to Formify

The simplest approach is to let users view the document in Formify:

GET https://docs-api.formify.se/v1/docs/{documentId}
Authorization: Bearer YOUR_ACCESS_TOKEN

The response includes documentUrl, which you can display as a link in your UI:

<a href="{documentUrl}" target="_blank">View document in Formify</a>

Option B: Download and Store PDF

To store the signed PDF in your own system:

GET https://docs-api.formify.se/v1/docs/{documentId}/signed-file
Authorization: Bearer YOUR_ACCESS_TOKEN

This endpoint returns a 302 Found redirect to a pre-signed S3 URL. Your HTTP client should automatically follow the redirect to download the PDF.

Pre-signed URL: The S3 URL is valid for approximately 10 minutes. If you need to download the file again later, make a new request to /signed-file.

Example: Download with cURL

curl -L -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  https://docs-api.formify.se/v1/docs/{documentId}/signed-file \
  -o signed-document.pdf

Example: Download with Node.js

const response = await fetch(
  `https://docs-api.formify.se/v1/docs/${documentId}/signed-file`,
  {
    headers: { 'Authorization': `Bearer ${accessToken}` },
    redirect: 'follow'
  }
);

const buffer = await response.arrayBuffer();
await fs.writeFile('signed-document.pdf', Buffer.from(buffer));

Complete Integration Example

Here is how all three flows work together in a typical integration:

// 1. User initiates document signing in your app
async function sendDocumentForSigning(pdfFile, signeeEmail) {
  const formData = new FormData();
  formData.append('file', pdfFile);

  const uploadResponse = await fetch('https://docs-api.formify.se/v1/files', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${accessToken}` },
    body: formData
  });
  const { fileId } = await uploadResponse.json();

  const docResponse = await fetch('https://docs-api.formify.se/v1/docs', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      fileId,
      name: 'Contract',
      signeeDetails: [{
        fullName: 'John Doe',
        emailAddress: signeeEmail,
        signatureType: 'bankid_identification',
        signaturePlacement: 'new_page'
      }]
    })
  });
  const { documentId } = await docResponse.json();

  await db.saveDocument(documentId, { status: 'pending' });
  return documentId;
}

// 2. Webhook handler (runs when document is completed)
app.post('/webhooks/formify', express.raw({ type: 'application/json' }), async (req, res) => {
  const timestamp = req.headers['x-formify-timestamp'];
  const signature = req.headers['x-formify-signature'];
  const secret = 'whsec_...';

  const signedPayload = `${timestamp}.${req.body}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  const isValid = crypto.timingSafeEqual(
    Buffer.from(expectedSignature),
    Buffer.from(signature)
  );

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  const { event, data } = JSON.parse(req.body);

  if (event.type === 'document.completed') {
    await db.updateDocument(data.documentId, { status: 'completed' });

    const pdfResponse = await fetch(
      `https://docs-api.formify.se/v1/docs/${data.documentId}/signed-file`,
      { headers: { 'Authorization': `Bearer ${accessToken}` }, redirect: 'follow' }
    );
    const pdfBuffer = await pdfResponse.arrayBuffer();

    await storage.savePDF(data.documentId, pdfBuffer);
    await sendEmail(user, 'Your document has been signed!');
  }

  res.status(200).send('OK');
});

Best Practices

Next Steps