May 27, 2026 · Tutorial

Build a Slack and Microsoft Teams Contract Approval Flow (2026)

Stop forcing team members to leave their work workspace to approve documents. Learn how to trigger Signbee contract requests and post interactive signing links directly inside Slack or Teams.

Michael Beckett
Michael Beckett

Founder, Signbee

TL;DR

You can automate your signing loop in chat apps. Setup a Slack or Teams interactive button, forward approval clicks to your backend, hit Signbee's single API endpoint to spin up a contract, and push the secure signing link back to the user. Use webhooks to update the channel instantly when the document is signed.

Why build document approvals into ChatOps?

Modern teams live in chat clients. Forcing managers, partners, or new hires to dig through their emails to find a contract signature request slows down cycles. Moving approvals to Slack or Microsoft Teams reduces friction and keeps work moving without context switching.

Whether it's an NDA request from a vendor, an internal expense approval, or a freelance statement of work, you can design an automated flow. This guide builds a robust, three-legged integration using custom webhook routes, Node.js, and Signbee.

Comparison: Slack Block Kit vs Teams Adaptive Cards

Platform FeatureSlack (Block Kit)Microsoft Teams
Payload formatBlock Kit JSONAdaptive Card JSON
Acknowledge timeout3,000 ms2,000 ms
Status updatesResponse URL webhookActivity ID replacement
Action typeInteractive buttons / menusAction.Execute / Action.Submit

The workflow architecture

The integration follows five distinct steps to handle generation, signing, and updating:

  1. Action: User clicks "Generate & Sign NDA" inside Slack or Teams.
  2. Trigger: Chat client sends an HTTP POST webhook request to your backend.
  3. Creation: Your backend verifies the signature and calls the Signbee API.
  4. Delivery: Backend updates the chat message with the secure Signbee signing URL.
  5. Completion: Signbee triggers a webhook on completion, notifying the channel.

Step 1: Handling Slack interactive events

When a user interacts with a Slack block (like clicking a button), Slack sends a POST payload to your configured Request URL. We must immediately respond with an HTTP 200 OK to satisfy the 3-second limit, then process the Signbee API call asynchronously.

Node.js & Express — Slack Interactive Endpoint
const express = require('express');
const fetch = require('node-fetch');
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.post('/slack/interactive', async (req, res) => {
  const payload = JSON.parse(req.body.payload);
  const { actions, response_url, user, channel } = payload;
  const actionValue = actions[0].value; // e.g., "approve_nda_req_99"

  // 1. Instantly respond to Slack to prevent timeouts (3s limit)
  res.status(200).send();

  // 2. Perform the async work (calling Signbee API)
  try {
    const signbeeDoc = await createSignbeeDocument(user, actionValue, response_url);
    
    // 3. Post signing URL back to Slack channel
    await updateSlackMessage(response_url, {
      text: `⚡ *NDA Prepared:* <${signbeeDoc.signing_url}|Click here to sign the document>`
    });
  } catch (err) {
    console.error('Failed to process flow:', err);
    await updateSlackMessage(response_url, {
      text: '❌ Error: Failed to generate document. Please try again.'
    });
  }
});

Step 2: Calling the Signbee API to generate the contract

Signbee lets you generate contracts on the fly using a single API endpoint. We pass Markdown text directly in the payload, dynamic signers, and custom metadata so we can map webhook events back to the original Slack message.

Calling Signbee API
async function createSignbeeDocument(user, recordId, responseUrl) {
  const response = await fetch('https://signb.ee/api/v1/documents', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${process.env.SIGNBEE_API_KEY}`
    },
    body: JSON.stringify({
      title: 'Mutual Non-Disclosure Agreement',
      markdown: `# Mutual Non-Disclosure Agreement\n\nThis NDA is executed between the undersigned parties in 2026...\n\n* Recipient Name: ${user.name}\n* Record Reference: ${recordId}`,
      signers: [
        {
          name: user.name,
          email: `${user.name.toLowerCase().replace(/\s+/g, '')}@company.com`,
          role: 'signer'
        }
      ],
      metadata: {
        slack_response_url: responseUrl,
        record_id: recordId
      }
    })
  });

  if (!response.ok) {
    throw new Error(`Signbee API returned status ${response.status}`);
  }

  return response.json();
}

Step 3: Updating status with Signbee webhooks

When a recipient completes the signing flow, Signbee sends a secure POST request to your webhook endpoint. We catch the document.signed event, extract our stored Slack response_url from the metadata, and update the original chat channel thread.

Signbee Webhook Listener
app.post('/webhooks/signbee', async (req, res) => {
  const { event, data } = req.body;

  if (event === 'document.signed') {
    const responseUrl = data.metadata.slack_response_url;
    const recordId = data.metadata.record_id;

    if (responseUrl) {
      await updateSlackMessage(responseUrl, {
        text: `🎉 *Contract Executed:* The NDA (Ref: ${recordId}) has been successfully signed by ${data.signers[0].name}.`,
        replace_original: false
      });
    }
  }

  res.status(200).json({ received: true });
});

async function updateSlackMessage(responseUrl, payload) {
  await fetch(responseUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload)
  });
}

Common security and design mistakes

MistakeConsequence
No request signature validationSpoof approval events easily
Ignoring response window limitsSlack displays persistent warning tags
Exposing internal Signbee keysAPI abuse / unauthorized templates

Frequently Asked Questions

How do you handle authentication between Slack/Teams and Signbee?

Slack/Teams calls your backend endpoint, which validates the request signature (e.g., verifying Slack's signing secret). Your backend then uses a secure environment variable containing your Signbee API key to authenticate requests against Signbee. The API key is never exposed to the client-side chat clients.

What are the response time requirements for chat interactive webhooks?

Slack requires applications to acknowledge interactive action payloads within 3,000 milliseconds (3 seconds) by returning an HTTP 200 OK. Teams has a similar limit of 2,000 milliseconds. To prevent timeout errors, your backend should immediately acknowledge the webhook, then process the Signbee API call and update the message asynchronously using the response URL.

Can we use templates in Signbee for automated Slack signing flows?

Yes. Signbee allows you to define templates using markdown. When triggering a document request from Slack or Teams, you can pass dynamic metadata or variables directly in the request body to customize the contract template instantly before generating the signing link.

Never drop a document — 5 free docs/month, 100 req/min.

Last updated: May 29, 2026 · Michael Beckett is the founder of Signbee and B2bee Ltd.

Related resources