Skip to main content
In this recipe, you build a small Node.js service that sends a contract when a CRM deal reaches the contract stage, and marks the deal as signed when every party has signed. It combines templates, field values, sending, and webhooks into one flow. The flow has two parts:
  1. Your CRM calls sendContract(deal). The service creates a document from a template, fills in the deal’s values, sets the customer as the signing party, and sends the document.
  2. sajn calls your webhook endpoint with document.completed when the sealed PDF is ready. The service downloads the PDF and updates the deal.

Before you begin

  • Node.js 18 or later, and Express: npm install express.
  • An API key in the SAJN_API_KEY environment variable. To create one, go to workspace settings in the sajn app, then Utvecklare (Developer) > API-nycklar (API keys).
  • A template with a party named Customer and FORM fields with the keys customer-name, org-number, and contract-value. Store its ID in SAJN_TEMPLATE_ID. For more information, see Create documents from templates.
  • A public HTTPS URL for the webhook endpoint, such as https://crm.example.com/webhooks/sajn.

Build the service

1

Subscribe to completed documents

Create the webhook once, and store the secret from the response, which starts with whsec_, in SAJN_WEBHOOK_SECRET. sajn returns the secret only in this response:
2

Write a small API client

Save the following file as sajn.js. It sends the version header on every request, throws on errors with the code and requestId, and takes an optional idempotency key:
3

Create and send the contract

Save the following file as send-contract.js. Each request has an idempotency key derived from the deal, so retrying sendContract for the same deal doesn’t create a second contract:
The create request returns the whole document, including its parties, so the service finds the customer’s party without another request. Store the returned document ID on the deal.
4

Handle the webhook

Save the following file as server.js. It verifies the Standard Webhooks signature over the raw body, answers right away, and then updates the deal:
All three events carry the document in data.object, in the shape that GET /api/v1/documents/:id returns. The handler deduplicates on the event id, which stays the same across retries. The url from GET /api/v1/documents/:id/files/SIGNED expires at expiresAt, so download the file right away. For more information about the signature headers, see Verify webhook signatures.

Run it

Start the server with node server.js, and call sendContract from your CRM’s deal-stage hook with a deal object such as the following:
Kai gets the invitation by email. When Kai signs, contracts/deal-12345.pdf appears and the server logs the deal update.

Handle errors

  • An error thrown by sajn includes the code and requestId. Branch on code, and log requestId so sajn support can find the request. For what each code means, see Errors.
  • 409 APPROVAL_REQUIRED on send: the API key’s user needs approval to send. Request it with POST /api/v1/approval-requests. When the request is approved, sajn sends the document for signing.
  • Values not written: a key doesn’t exist on the template (NOT_FOUND), or a value doesn’t match the field’s type (VALIDATION_FAILED).
  • If your endpoint is down, sajn retries the delivery. For more information, see Delivery and retries.