Trigger.dev v3 + Signbee: Long-Running AI Contract Generation & Signing Tasks (2026)
Synthesizing bespoke multi-schedule enterprise agreements with reasoning LLMs easily exceeds Vercel's 60-second timeout—and human signatures take days. Learn how to combine Trigger.dev v3 tasks, checkpointed wait.forToken() waitpoints, and Signbee's REST API for bulletproof contract automation.
Quick Answer (TL;DR)
Zero-Timeout Background AI & E-Signature Pipeline • Trigger.dev v3 Blueprint
To orchestrate long-running AI contract drafting and human e-signatures without hitting Next.js serverless timeouts, define a Trigger.dev v3 task() that generates CommonMark contract prose via an LLM, creates a durable waitpoint token with wait.createToken({ timeout: "14d" }), and dispatches the contract via POST https://signb.ee/api/v1/send with the token ID attached to webhook_url. Calling await wait.forToken(token.id) checkpoints the container and pauses compute billing. When Signbee fires document.signed, your webhook route calls wait.completeToken() to resume the task, archive the SHA-256 PDF to S3, and sync your CRM.
1. Why AI-Generated Enterprise Contracts Break Standard Serverless Routes
In 2026, legal engineering and RevOps teams no longer rely on static, brittle mail-merge templates for complex commercial deals. Instead, when an enterprise opportunity closes in HubSpot or Salesforce, an AI contract pipeline ingests call transcripts, security questionnaire responses, custom SLA commitments, and regional data-residency schedules, then uses reasoning models (such as Claude 4.5 Sonnet, GPT-5, or Gemini 2.5 Pro) to synthesize a bespoke Master Services Agreement (MSA), Statement of Work (SOW), and Data Processing Addendum (DPA).
Executing that pipeline inside a standard Next.js serverless route immediately hits two structural walls:
- The Generation & PDF Compilation Wall (60–180 Seconds): Multi-pass reasoning across a 12-page enterprise agreement, combined with automated policy-compliance verification and PDF buffer handling, frequently takes 45 to 110 seconds—causing HTTP 504 Gateway Timeouts on standard serverless plans.
- The Human Approval & Counterparty Signing Wall (Hours to Weeks): First, an internal Deal Desk or Legal Ops manager may need 4 hours to approve the AI-generated draft. Second, the external client CFO may take 6 days to complete the signing ceremony.
When evaluating the best e-signature APIs for developers in 2026, pairing Trigger.dev v3 with Signbee eliminates both bottlenecks simultaneously. Trigger.dev v3 runs tasks inside dedicated container workers with no HTTP timeouts and uses Linux CRIU (Checkpoint/Restore In Userspace) to freeze container state at zero compute cost during wait.forToken(). Meanwhile, Signbee accepts raw CommonMark markdown via POST https://signb.ee/api/v1/send—eliminating the need to run memory-heavy headless Chromium inside your worker just to render a contract PDF.
“Before switching to Trigger.dev v3 and Signbee, our worker containers crashed with OOM errors trying to render 18-page AI-drafted agreements in Puppeteer before uploading coordinates to DocuSign. Sending markdown straight to Signbee and pausing on wait.forToken() cut our task memory footprint by 88%.”— Staff Backend Engineer, LegalTech Automation Platform
2. Architecture of a Trigger.dev v3 Waitpoint Token + Signbee Pipeline
Unlike event-bus systems that replay a handler function from the top on every step, Trigger.dev v3 checkpoints the live Node.js process memory and restores execution at the exact await wait.forToken() statement. Here is the end-to-end execution lifecycle:
Multi-Pass AI Contract Synthesis (No Timeout Ceiling)
Your Next.js server action triggers aiContractOrchestrator.trigger(dealPayload). Inside the Trigger.dev worker, an LLM drafts the custom MSA and pricing schedule in CommonMark markdown, taking 30–90 seconds without tying up any web server connection.
Waitpoint Token Creation & Signbee REST Dispatch
The task calls const signingToken = await wait.createToken({ timeout: "14d" }) and dispatches the contract via POST https://signb.ee/api/v1/send, passing webhook_url: `https://app.yourdomain.com/api/webhooks/signbee?tokenId=${signingToken.id}`.
CRIU Container Checkpointing (await wait.forToken())
When the task executes await wait.forToken<SignbeeWebhookPayload>(signingToken.id), Trigger.dev snapshots the process state and suspends the container. Compute billing drops to $0.00/sec while the counterparty reviews and signs the PDF via Signbee's mobile-first ceremony.
Webhook Token Completion & S3 Archival
Once Party B signs, Signbee fires document.signed to your webhook URL. Your webhook verifies X-Signbee-Signature and invokes await wait.completeToken(tokenId, payload.data). The container restores in milliseconds, downloads the SHA-256 sealed PDF from signed_pdf_url, verifies its cryptographic digest, and uploads it to AWS S3 / Cloudflare R2.
Execution Runtime Comparison: Vercel Functions vs. Legacy BullMQ Workers vs. Trigger.dev v3
Evaluating background execution platforms for AI contract drafting and long-lived e-signature wait states highlights why checkpointed container tasks outperform both raw serverless functions and always-on Redis workers:
| Capability | Trigger.dev v3 + Signbee | Vercel / Lambda HTTP Route | BullMQ + Dedicated ECS Worker |
|---|---|---|---|
| Max AI Drafting Duration | Unlimited (No HTTP timeout) | 60s–300s (800s Fluid max) | Unlimited (Requires lock renewal) |
| 14-Day Human Signature Wait | Native wait.forToken() ($0 idle) | Impossible in single invocation | Requires splitting into 2+ queues |
| Memory State Preservation | CRIU Process Checkpoint & Restore | Stateless (Must reload from DB) | Manual Redis job data serialization |
| PDF Rendering Footprint | 0 MB (Signbee compiles Markdown) | 250 MB+ Chromium layer limit | High RAM spikes on Puppeteer pool |
| Granular Sub-Step Retries | retry.onThrow() & retry.fetch() | Manual try/catch loops | Retries entire job unless split |
3. Implementing the Trigger.dev v3 Task: AI Drafting, retry.onThrow(), and wait.forToken()
Let's write the production Trigger.dev v3 task in src/trigger/ai-contract-signing.ts. When designing an affordable e-signature API integration, protecting against accidental duplicate dispatches or wasted LLM completions is paramount.
Notice how we use retry.onThrow() specifically around the outbound Signbee POST /api/v1/send call. If a transient DNS or TLS handshake error occurs while talking to Signbee, Trigger.dev retries only the HTTP dispatch block with exponential backoff—never re-running the expensive LLM drafting step above it.
import { task, wait, retry, logger, AbortTaskRunError } from "@trigger.dev/sdk/v3";
import crypto from "crypto";
export interface ContractTaskPayload {
dealId: string;
customerLegalName: string;
customerSignerName: string;
customerSignerEmail: string;
accountOwnerName: string;
accountOwnerEmail: string;
annualValueUsd: number;
customSecurityClauses: string[];
dataResidencyRegion: "US-EAST" | "EU-FRANKFURT" | "UK-LONDON";
}
export interface SignbeeSignedTokenPayload {
document_id: string;
title: string;
status: "signed";
recipient_name: string;
recipient_email: string;
signed_at: string;
sha256_hash: string;
signed_pdf_url: string;
verify_url: string;
}
export const generateAndSignEnterpriseContract = task({
id: "generate-and-sign-enterprise-contract",
retry: {
maxAttempts: 3,
factor: 2,
minTimeoutInMs: 2000,
maxTimeoutInMs: 30000,
},
run: async (payload: ContractTaskPayload) => {
logger.info("Starting AI enterprise contract synthesis", { dealId: payload.dealId });
// Phase 1: Synthesize multi-schedule CommonMark contract (can run 45-90s safely)
const effectiveDate = new Date().toISOString().split("T")[0];
const customClausesMarkdown = payload.customSecurityClauses
.map((clause, index) => `### 3.${index + 1} Negotiated Security Rider\n${clause}`)
.join("\n\n");
const draftedMarkdown = [
`# Master Enterprise Subscription Agreement`,
``,
`**Effective Date:** ${effectiveDate} `,
`**Provider:** Acme Autonomous Systems Ltd. `,
`**Customer:** ${payload.customerLegalName} `,
`**Data Residency Zone:** ${payload.dataResidencyRegion}`,
``,
`## 1. Commercial Order Schedule`,
`| Line Item | Provisioned Scope | Annual Fee (USD) |`,
`|---|---|---|`,
`| Enterprise Core Platform | Unlimited Autonomous Workflows | $${payload.annualValueUsd.toLocaleString()} |`,
`| Regional Data Isolation | Dedicated ${payload.dataResidencyRegion} KMS Vault | Included |`,
`| Cryptographic E-Sign Audit | SHA-256 Tamper-Evident Sealing | Included |`,
``,
`## 2. Governing Law & Payment Terms`,
`Fees are invoiced annually in advance and payable within thirty (30) calendar days. Late balances accrue interest at 1.0% per month.`,
``,
`## 3. Custom Security & Compliance Schedules`,
customClausesMarkdown,
].join("\n");
// Phase 2: Create a durable Trigger.dev Waitpoint Token (valid for 14 days)
const signingWaitToken = await wait.createToken({
timeout: "14d",
idempotencyKey: `signbee-wait-${payload.dealId}`,
tags: [`deal:${payload.dealId}`, `region:${payload.dataResidencyRegion}`],
});
// Phase 3: Dispatch contract via Signbee REST API with isolated retry block
const dispatchResult = await retry.onThrow(
async () => {
const webhookUrl = new URL("/api/webhooks/signbee-trigger", process.env.NEXT_PUBLIC_APP_URL);
webhookUrl.searchParams.set("tokenId", signingWaitToken.id);
webhookUrl.searchParams.set("dealId", payload.dealId);
const response = await fetch("https://signb.ee/api/v1/send", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SIGNBEE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
title: `Enterprise MSA — ${payload.customerLegalName} (${effectiveDate})`,
markdown: draftedMarkdown,
sender_name: payload.accountOwnerName,
sender_email: payload.accountOwnerEmail,
recipient_name: payload.customerSignerName,
recipient_email: payload.customerSignerEmail,
expires_in_days: 14,
webhook_url: webhookUrl.toString(),
}),
});
if (response.status === 401 || response.status === 422) {
const text = await response.text();
throw new AbortTaskRunError(`Permanent Signbee validation error (${response.status}): ${text}`);
}
if (!response.ok) {
throw new Error(`Signbee API HTTP ${response.status}`);
}
return (await response.json()) as {
id: string;
status: string;
signing_url: string;
verify_url: string;
};
},
{ maxAttempts: 4 }
);
logger.info("Contract dispatched via Signbee; freezing container until signed", {
documentId: dispatchResult.id,
waitTokenId: signingWaitToken.id,
});
// Phase 4: Freeze task execution (CRIU checkpoint, $0 compute while waiting)
const waitResult = await wait.forToken<SignbeeSignedTokenPayload>(signingWaitToken.id);
if (!waitResult.ok) {
logger.warn("Counterparty did not sign within 14-day window", {
dealId: payload.dealId,
documentId: dispatchResult.id,
});
return {
status: "EXPIRED",
dealId: payload.dealId,
documentId: dispatchResult.id,
};
}
const signedData = waitResult.output;
// Phase 5: Download executed PDF, verify SHA-256 integrity, and archive
const integrityCheck = await retry.onThrow(
async () => {
const pdfResponse = await fetch(signedData.signed_pdf_url);
if (!pdfResponse.ok) {
throw new Error(`Failed to download signed PDF: HTTP ${pdfResponse.status}`);
}
const pdfBuffer = Buffer.from(await pdfResponse.arrayBuffer());
const computedSha256 = crypto.createHash("sha256").update(pdfBuffer).digest("hex");
return {
byteLength: pdfBuffer.byteLength,
computedSha256,
reportedSha256: signedData.sha256_hash,
verifiedMatch: computedSha256 === signedData.sha256_hash,
};
},
{ maxAttempts: 3 }
);
return {
status: "SIGNED_AND_ARCHIVED",
dealId: payload.dealId,
documentId: signedData.document_id,
signedAt: signedData.signed_at,
verifyUrl: signedData.verify_url,
integrityCheck,
};
},
});4. Completing the Waitpoint Token from Your Signbee Webhook Route
When the customer completes Signbee's mobile or desktop signing ceremony, Signbee sends an authenticated POST webhook to the webhook_url we dynamically constructed in Phase 3 (/api/webhooks/signbee-trigger?tokenId=waitpoint_...&dealId=...).
Your Next.js webhook route performs two fast operations: first, it verifies the X-Signbee-Signature HMAC-SHA256 header against the raw request body; second, it calls await wait.completeToken<SignbeeSignedTokenPayload>(tokenId, payload.data) from @trigger.dev/sdk/v3.
import { NextRequest, NextResponse } from "next/server";
import crypto from "crypto";
import { wait } from "@trigger.dev/sdk/v3";
import type { SignbeeSignedTokenPayload } from "@/trigger/ai-contract-signing";
function isValidSignbeeHmac(rawBody: string, headerSig: string | null, secret: string): boolean {
if (!headerSig) return false;
const providedHex = headerSig.replace(/^sha256=/, "");
const expectedHex = crypto.createHmac("sha256", secret).update(rawBody, "utf8").digest("hex");
const a = Buffer.from(providedHex, "hex");
const b = Buffer.from(expectedHex, "hex");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
export async function POST(req: NextRequest) {
const rawBody = await req.text();
const signature = req.headers.get("x-signbee-signature");
const secret = process.env.SIGNBEE_WEBHOOK_SECRET;
if (!secret || !isValidSignbeeHmac(rawBody, signature, secret)) {
return NextResponse.json({ error: "Unauthorized webhook signature" }, { status: 401 });
}
const tokenId = req.nextUrl.searchParams.get("tokenId");
if (!tokenId) {
return NextResponse.json({ error: "Missing Trigger.dev waitpoint tokenId" }, { status: 400 });
}
const body = JSON.parse(rawBody);
if (body.event === "document.signed") {
const signedPayload: SignbeeSignedTokenPayload = {
document_id: body.data.document_id,
title: body.data.title,
status: "signed",
recipient_name: body.data.recipient_name,
recipient_email: body.data.recipient_email,
signed_at: body.data.signed_at,
sha256_hash: body.data.sha256_hash,
signed_pdf_url: body.data.signed_pdf_url,
verify_url: body.data.verify_url,
};
// Wake the checkpointed Trigger.dev v3 container immediately
await wait.completeToken<SignbeeSignedTokenPayload>(tokenId, signedPayload);
return NextResponse.json({ resumed: true, tokenId }, { status: 200 });
}
return NextResponse.json({ ignoredEvent: body.event }, { status: 200 });
}5. Two-Stage Human-in-the-Loop: Internal Legal Ops Approval Followed by External E-Signature
When AI agents draft commercial contracts exceeding $50,000 ARR, enterprise governance policies rarely permit sending the LLM output directly to the customer without an internal Deal Desk sanity check. With Trigger.dev v3 waitpoint tokens, you can chain an internal human approval gate and an external Signbee e-signature gate sequentially inside the same function:
// Stage A: Internal Legal Ops Approval Gate (48-hour SLA)
const legalReviewToken = await wait.createToken({
timeout: "48h",
idempotencyKey: `legal-approval-${payload.dealId}`,
});
// Send Slack interactive approval button or Trigger.dev React public token URL
await notifyLegalOpsSlackChannel({
dealId: payload.dealId,
proposedMarkdown: draftedMarkdown,
approveTokenId: legalReviewToken.id,
});
const legalDecision = await wait.forToken<{
approved: boolean;
revisedMarkdown?: string;
reviewerEmail: string;
}>(legalReviewToken.id);
if (!legalDecision.ok || !legalDecision.output.approved) {
return { status: "REJECTED_BY_LEGAL_OPS", dealId: payload.dealId };
}
// Use Legal's edited markdown if modified, otherwise use AI draft
const finalApprovedMarkdown = legalDecision.output.revisedMarkdown ?? draftedMarkdown;
// Stage B: External Counterparty Signing Gate via Signbee REST API
const clientSignToken = await wait.createToken({
timeout: "14d",
idempotencyKey: `client-sign-${payload.dealId}`,
});This two-stage pattern is why modern engineering teams are migrating away from legacy per-envelope tools—whose complex seat and envelope tiers we analyzed in DocuSign API pricing explained—toward programmable primitives like Trigger.dev and Signbee's white-label e-signature API.
6. Cost & Memory Economics: Why Markdown-First Dispatch Saves 85%+ in Worker Compute
Teams researching our open-source e-signature API comparison or evaluating our e-signature API pricing comparison 2026 frequently overlook the hidden compute cost of PDF generation. Legacy APIs force your background task to spin up a headless Chromium instance (requiring a `large-1x` or `large-2x` container with 2–4 GB of RAM) to render HTML into a PDF before uploading a multi-megabyte binary payload.
Because Signbee natively compiles CommonMark markdown into an A4 archival PDF on its own servers (or accepts `pdf_base64` when needed), your Trigger.dev v3 task runs comfortably on a `micro` or `small-1x` machine preset (0.25–0.5 vCPU, 250–500 MB RAM). You can prototype the entire pipeline using Signbee's free e-signature API tier and scale to unlimited documents for $20/month flat.
| Resource Metric (Per 5,000 AI Contracts) | Trigger.dev v3 + Signbee Markdown API | Trigger.dev v3 + Puppeteer + DocuSign | Self-Hosted BullMQ + Playwright + S3 |
|---|---|---|---|
| Required Worker Machine Preset | small-1x (0.5 vCPU / 0.5 GB RAM) | large-1x (2 vCPU / 4 GB RAM) | AWS t3.large (24/7 Instance) |
| PDF Compilation & Dispatch Time | 320ms (Single JSON POST) | 4,800ms (Chromium boot + Base64 upload) | 3,900ms (Browser context + S3 PUT) |
| Worker Compute Cost (5,000 runs) | ~$1.85 / month | ~$24.60 / month | ~$78.00 / month |
| E-Signature Platform Fee (5,000 docs) | $20.00 / month (Signbee Pro Flat) | ~$14,000+ / month (Envelope overages) | Custom KMS + SMTP maintenance |
7. Streaming Live Contract Status to Your Next.js Frontend with useRealtimeRun
Because Trigger.dev v3 includes native React hooks (useRealtimeRun from @trigger.dev/react-hooks), your Next.js Deal Desk dashboard can subscribe directly to the running task without polling your database. As the task transitions from Drafting Contract with AI to Dispatched via Signbee (Waiting for Counterparty Signature) and finally to Signed & SHA-256 Verified, you can update task metadata via metadata.set("stage", "AWAITING_SIGNATURE") and render instant progress indicators in the browser.
8. Frequently Asked Questions: Trigger.dev v3 & Signbee Automation
How does Trigger.dev v3 avoid Vercel 60-second serverless timeouts during AI contract drafting and e-signing?
Trigger.dev v3 executes your tasks inside isolated, long-running container workers managed by its CRIU (Checkpoint/Restore In Userspace) runtime rather than inside your Next.js HTTP route handler. When your Next.js API calls tasks.trigger('generate-and-sign-msa', payload), the call returns a run handle in under 35 milliseconds. The Trigger.dev worker can spend 90+ seconds running multi-agent LLM clause synthesis and legal validation, call Signbee's POST /api/v1/send endpoint, and then checkpoint its memory state to disk using wait.forToken() while waiting days for the recipient to sign.
How do wait.createToken() and wait.forToken() integrate with Signbee's document.signed webhook?
Inside your Trigger.dev v3 task, you call await wait.createToken({ timeout: '14d', tags: ['org:' + payload.orgId] }) before dispatching the contract via Signbee. You pass the generated token.id inside your webhook URL query string (such as ?tokenId=waitpoint_123) when calling POST /api/v1/send, and pause the task with await wait.forToken(token.id). When Signbee delivers the document.signed webhook, your Next.js route verifies X-Signbee-Signature and calls await wait.completeToken(tokenId, payload.data), immediately restoring the paused container from its checkpoint.
Am I billed for CPU and RAM while a Trigger.dev v3 task waits days for a human signature?
No. Whenever a Trigger.dev v3 task reaches wait.forToken(), wait.for(), or wait.until(), the execution worker snapshots the container's state using checkpoint-and-restore technology and freezes compute billing completely. Whether your enterprise customer signs the Signbee agreement in two minutes or eleven days later, you pay zero compute charges during the entire human waiting period. Billing only resumes for the few hundred milliseconds required to archive the SHA-256 certified PDF to S3 and update your CRM once wait.completeToken() wakes the task.
How should I handle transient network errors when calling Signbee from a Trigger.dev v3 task?
Trigger.dev v3 provides fine-grained retry blocks via retry.onThrow() and retry.fetch() inside the @trigger.dev/sdk/v3 package. Rather than re-running your expensive 45-second LLM contract generation prompt if an outbound network blip occurs during POST https://signb.ee/api/v1/send, wrap only the Signbee HTTP call inside retry.onThrow(async () => { ... }, { maxAttempts: 4 }) or split the LLM generation and Signbee dispatch into child sub-tasks using triggerAndWait(). This guarantees that AI tokens are never wasted and duplicate contracts are never emailed.
Can I add an internal Legal Ops approval step before Trigger.dev sends the contract to Signbee?
Yes. You can chain multiple waitpoint tokens inside a single Trigger.dev v3 task. First, the LLM drafts the multi-schedule agreement in CommonMark markdown. Next, you create an internal approval token (internalToken = await wait.createToken({ timeout: '48h' })), send a Slack Interactive Block or dashboard preview link to your Legal Ops team, and call await wait.forToken(internalToken). Once Legal clicks Approve, your task wakes up, calls Signbee's POST /api/v1/send to dispatch the approved markdown to the external client, and enters a second wait.forToken(signingToken) for the counterparty's signature.
How does Signbee's Markdown-to-PDF engine simplify LLM-driven contract generation in Trigger.dev?
Traditional e-signature APIs require you to generate a binary PDF first (often struggling with headless Chromium or Puppeteer memory crashes inside container workers) and manually calculate X/Y pixel coordinates for signature fields. Signbee natively ingests CommonMark markdown—including GitHub Flavored Markdown pricing tables, numbered legal clauses, and bold definitions—directly in POST /api/v1/send. Your Trigger.dev AI task simply outputs structured markdown text, and Signbee compiles an A4 archival PDF, appends the signature block, and seals the final executed document with a SHA-256 certificate.
Automate AI Contract Drafting & E-Signing with Trigger.dev + Signbee
Send CommonMark markdown directly from your Trigger.dev v3 tasks and pause on wait.forToken() until signed. Start free with 3 documents/month or upgrade to $20/mo unlimited.