GuideAugust 15, 2026 · 12 min read

Stripe Invoicing + E-Signatures: Auto-Attach Signed Agreements (2026)

Eliminate chargebacks, accelerate five-figure B2B deal velocity, and close the loop between payment collection and legal execution. Here is how to architect an automated two-way handshake between Stripe Billing and Signbee.

Michael Beckett
Michael Beckett

Founder, Signbee · Ex-Stripe ecosystem builder

Architectural Overview

Sending a $10,000+ Stripe invoice without an attached, cryptographically signed Master Services Agreement (MSA) is a massive financial vulnerability. If a client initiates a credit card dispute or ACH reversal, banks routinely rule against merchants lacking explicit, signed contract terms. By binding Stripe Billing webhooks directly to Signbee's markdown-native e-signature API, you achieve an autonomous workflow: generating invoices automatically triggers custom contract dispatch, signature execution auto-charges the card, and the signed PDF with its SHA-256 audit trail is permanently anchored to the Stripe Invoice record.

The High-Value Invoice Dilemma: Why Stripe Receipts Aren't Enough

Stripe Invoicing is the global gold standard for automated B2B billing, recurring retainers, and enterprise accounts receivable. But payment rails were engineered to move monetary balances—not to establish legal enforceability. When your company issues an invoice exceeding $2,500 for custom software engineering, strategic consulting, or annual SaaS tiers, a standard Stripe hosted invoice page or email receipt contains only line items, pricing tiers, and tax IDs.

When customer relationships fray, financial controllers face three major structural risks:

  • Card-Not-Present (CNP) Chargebacks & Friendly Fraud: Disgruntled buyers, cash-strapped startups, or executives experiencing buyer's remorse frequently dispute credit card charges through issuing banks under reason codes like "Services Not Rendered" (Visa 13.1, Mastercard 4853) or "Unauthorized Transaction" (Visa 10.4, Mastercard 4837). In the absence of a signed contract agreeing to payment schedules and deliverables, banks rule against the merchant in over 80% of cases.
  • Scope Creep & Ambiguous Deliverables: Without explicit payment terms, milestone sign-offs, and limitation of liability clauses tied directly to the invoice balance, disputes frequently devolve into protracted legal battles.
  • Manual Operations Friction: In traditional operations, sales reps create an invoice in Stripe, draft a PDF in Google Docs, upload it to legacy tools like DocuSign, email the signer, manually monitor for completion, and then manually mark the invoice ready for payment. This fragmented process introduces 3 to 7 days of sales latency.

To learn how modern software companies eliminate this drag, read our comprehensive guides on automating invoice signing via API and embedding e-signatures in SaaS products.

The 2-Way Handshake Architecture

The automated pipeline between Stripe Billing and Signbee is structured as an event-driven, bidirectional state machine. Neither billing nor legal execution acts in isolation; each state transition in Stripe triggers a corresponding legal lifecycle event, and each legal verification updates Stripe.

Event Flow: From Draft Invoice to Cryptographic Settlement

  1. 1
    Stripe Event Emitted: A sales action, CRM trigger, or API call creates an invoice in Stripe (invoice.created or invoice.finalized) with requires_contract: true metadata.
  2. 2
    Dynamic MSA Generation: Your backend receives the webhook, extracts line items, payment terms, and client information, and dynamically compiles a bespoke contract in Markdown.
  3. 3
    Signbee Contract Dispatch: Your server posts the document to Signbee's /api/v1/send endpoint, linking the Stripe Invoice ID in custom metadata. Signbee converts the Markdown into an immutable, formatted document and dispatches signing invites.
  4. 4
    Client Signs with Cryptographic Proof: The client signs on web or mobile. Signbee registers the signer's IP, timestamp, user agent, and generates a SHA-256 certificate of completion.
  5. 5
    Signed Webhook Callback & Settlement: Signbee emits document.signed. Your backend uploads the signed PDF directly to Stripe via the Files API, attaches it to the Invoice object, updates invoice metadata with the SHA-256 hash, and calls stripe.invoices.pay().
Lifecycle PhaseStripe StateSignbee StateAction Executed
Initiationdraft / openpendingGenerate Markdown agreement & dispatch
Awaiting Signatureauto_advance: falsedelivered / viewedHold invoice payment execution
Signed & Verifiedmetadata.signed: truesignedUpload PDF to Stripe Files API
SettlementpaidarchivedTrigger automatic charge or send receipt

Bulletproofing Against Chargebacks: The Cryptographic Evidence Chain

In B2B credit card transactions and automated ACH debits, payment disputes are governed by strict card network operating regulations. When defending a high-value dispute under Visa Compelling Evidence 3.0 (CE 3.0) or Mastercard Dispute Resolution Rules, the merchant bears the burden of proof to demonstrate that the cardholder authorized the exact terms of the transaction.

A simple email confirmation or checkout checkbox is routinely discarded during dispute arbitration because it fails to prove identity binding. Under the ESIGN Act, UETA, and eIDAS regulations, a legally binding electronic contract requires:

  • Intent to Sign: Explicit digital action demonstrating willful consent to the agreement.
  • Consent to Electronic Business: Clear statutory disclosure acknowledged prior to signing.
  • Tamper-Evident Association: Cryptographic anchoring linking the signature directly to the exact text of the document at signing time.
  • Comprehensive Audit Trail: Timestamped records of every interaction from dispatch to completion.

What Signbee's SHA-256 Audit Trail Provides to Stripe Radar

Every agreement signed via Signbee produces an immutable cryptographic certificate. When Stripe Radar or your dispute manager submits evidence to Visa, Mastercard, or American Express, the audit package includes:

Document ChecksumSHA256: 7f83b1657ff1fc53b92dc...
Signer IP & Geo-Location198.51.100.42 (San Francisco, CA, US)
RFC 3161 Timestamp2026-08-15T14:32:09.112Z
Stripe Invoice Bindingin_1Pk92xLkj9281Zxq99201aB

Full Implementation: Next.js & TypeScript Webhook Engine

Let's build a production-grade webhook receiver and dispatcher using Next.js App Router (or Node.js Express). This service handles two core events:

  1. Stripe invoice.created / invoice.finalized: Reads invoice line items, builds a dynamic Markdown contract, and calls Signbee's API.
  2. Signbee document.signed: Downloads the signed PDF, uploads it to the Stripe Files API as an invoice_attachment, stores the SHA-256 hash in invoice metadata, and captures payment.
app/api/webhooks/stripe/route.ts — Stripe Invoice Webhook HandlerTypeScript
import { NextRequest, NextResponse } from "next/server";
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: "2024-06-20",
});

const STRIPE_WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET!;
const SIGNBEE_API_KEY = process.env.SIGNBEE_API_KEY!;
const HIGH_VALUE_THRESHOLD_CENTS = 250000; // $2,500.00

export async function POST(req: NextRequest) {
  const body = await req.text();
  const signature = req.headers.get("stripe-signature");

  if (!signature) {
    return NextResponse.json({ error: "Missing stripe-signature header" }, { status: 400 });
  }

  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(body, signature, STRIPE_WEBHOOK_SECRET);
  } catch (err: any) {
    console.error(`Webhook signature verification failed: ${err.message}`);
    return NextResponse.json({ error: "Invalid signature" }, { status: 400 });
  }

  // Handle invoice creation
  if (event.type === "invoice.created" || event.type === "invoice.finalized") {
    const invoice = event.data.object as Stripe.Invoice;

    // Check if contract is required based on amount or metadata tag
    const requiresContract =
      invoice.metadata?.requires_contract === "true" ||
      (invoice.amount_due && invoice.amount_due >= HIGH_VALUE_THRESHOLD_CENTS);

    // Skip if already attached or does not qualify
    if (!requiresContract || invoice.metadata?.signbee_doc_id) {
      return NextResponse.json({ received: true, skipped: true });
    }

    const customerEmail = invoice.customer_email || "";
    const customerName = invoice.customer_name || "Valued Client";

    // Format line items into Markdown table
    const lineItemRows = (invoice.lines?.data || [])
      .map(
        (item) =>
          `| ${item.description || "Service"} | ${item.quantity || 1} | $${((item.unit_amount_excluding_tax || item.amount) / 100).toFixed(2)} | $${(item.amount / 100).toFixed(2)} |`
      )
      .join("\n");

    const totalFormatted = (invoice.amount_due / 100).toFixed(2);
    const invoiceNumber = invoice.number || invoice.id;

    // Compile dynamic Markdown agreement
    const markdownAgreement = `
# Master Services Agreement & Order Form

**Agreement Reference:** ${invoiceNumber}  
**Effective Date:** ${new Date().toISOString().split("T")[0]}  
**Provider:** Acme Solutions Inc.  
**Client Organization:** ${customerName}  
**Signer Email:** ${customerEmail}  

---

## 1. Statement of Work & Deliverables

The parties agree to the scope and line items detailed below, corresponding directly to Stripe Invoice `${invoice.id}`:

| Service Description | Quantity | Rate | Total Due |
| :--- | :--- | :--- | :--- |
${lineItemRows}

**Total Contract Value:** $${totalFormatted} ${invoice.currency.toUpperCase()}

---

## 2. Payment Terms & Automatic Collection

1. **Authorization:** By signing below, Client certifies that they are an authorized representative with authority to execute contracts and authorize payment.
2. **Immediate Settlement:** Client acknowledges that execution of this agreement authorizes Acme Solutions Inc. to process payment for Invoice `${invoiceNumber}` immediately upon signature.
3. **Dispute Waiver:** Client agrees that services outlined herein constitute authorized deliverables, and waives claims of unauthorized card-not-present processing once delivery is initiated.

---

## 3. Execution & Cryptographic Authentication

By signing below, the parties agree to be legally bound by this Master Services Agreement under the ESIGN Act (15 U.S.C. § 7001) and UETA regulations.
`;

    // Dispatch agreement via Signbee API
    const signbeeRes = await fetch("https://signb.ee/api/v1/send", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${SIGNBEE_API_KEY}`,
      },
      body: JSON.stringify({
        markdown: markdownAgreement,
        recipient_name: customerName,
        recipient_email: customerEmail,
        subject: `Action Required: Sign Agreement for Invoice ${invoiceNumber}`,
        metadata: {
          stripe_invoice_id: invoice.id,
          stripe_customer_id: typeof invoice.customer === "string" ? invoice.customer : invoice.customer?.id,
          total_cents: invoice.amount_due,
        },
      }),
    });

    if (!signbeeRes.ok) {
      const errText = await signbeeRes.text();
      console.error("Failed to send contract via Signbee:", errText);
      return NextResponse.json({ error: "Signbee dispatch failed" }, { status: 500 });
    }

    const { id: signbeeDocId } = await signbeeRes.json();

    // Update Stripe Invoice metadata to record the pending contract state
    await stripe.invoices.update(invoice.id, {
      metadata: {
        signbee_doc_id: signbeeDocId,
        contract_status: "sent_for_signing",
        contract_dispatched_at: new Date().toISOString(),
      },
      auto_advance: false, // Pause auto-collection until signed
    });

    return NextResponse.json({ received: true, signbeeDocId });
  }

  return NextResponse.json({ received: true });
}

Next, implement the Signbee webhook listener. When the client executes the e-signature, Signbee posts a document.signed event containing the document checksum, completed PDF download link, and audit trail metadata.

app/api/webhooks/signbee/route.ts — Signbee Signature Callback HandlerTypeScript
import { NextRequest, NextResponse } from "next/server";
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: "2024-06-20",
});

export async function POST(req: NextRequest) {
  const payload = await req.json();

  // Validate event type
  if (payload.event !== "document.signed") {
    return NextResponse.json({ received: true, ignored: true });
  }

  const {
    document_id: signbeeDocId,
    sha256_hash: sha256Hash,
    signed_pdf_url: signedPdfUrl,
    metadata,
  } = payload;

  const stripeInvoiceId = metadata?.stripe_invoice_id;
  if (!stripeInvoiceId) {
    console.error("Signbee callback missing stripe_invoice_id metadata");
    return NextResponse.json({ error: "Missing invoice reference" }, { status: 400 });
  }

  try {
    // 1. Fetch the signed PDF certificate from Signbee
    const pdfResponse = await fetch(signedPdfUrl);
    if (!pdfResponse.ok) {
      throw new Error(`Failed to download signed PDF: ${pdfResponse.statusText}`);
    }
    const pdfBuffer = Buffer.from(await pdfResponse.arrayBuffer());

    // 2. Upload the signed PDF directly to Stripe Files API
    const stripeFile = await stripe.files.create({
      purpose: "invoice_attachment",
      file: {
        data: pdfBuffer,
        name: `signed_agreement_${signbeeDocId}.pdf`,
        type: "application/pdf",
      },
    });

    // 3. Attach file to Invoice and update metadata with SHA-256 hash
    await stripe.invoices.update(stripeInvoiceId, {
      invoice_attachments: [stripeFile.id],
      metadata: {
        contract_status: "signed",
        signbee_audit_hash: sha256Hash,
        signbee_signed_file_id: stripeFile.id,
        signed_at: new Date().toISOString(),
      },
      auto_advance: true, // Resume automated lifecycle
    });

    // 4. Optionally capture payment immediately if auto-charge is configured
    const invoice = await stripe.invoices.retrieve(stripeInvoiceId);
    if (invoice.status === "open" && invoice.collection_method === "charge_automatically") {
      await stripe.invoices.pay(stripeInvoiceId);
      console.log(`Successfully paid invoice ${stripeInvoiceId} following signature.`);
    }

    return NextResponse.json({
      success: true,
      stripeFileId: stripeFile.id,
      sha256Hash,
    });
  } catch (error: any) {
    console.error("Error executing post-signing Stripe handshake:", error);
    return NextResponse.json({ error: error.message }, { status: 500 });
  }
}

Submitting Dispute Evidence Programmatically via Stripe Disputes API

In the rare event a cardholder initiates a chargeback on an invoice, you don't have to manually hunt down contracts in external folders. Because the signed PDF and SHA-256 hash are bound to the invoice metadata and Files API, you can programmatically submit dispute evidence directly to card networks:

TypeScript — Programmatic Stripe Dispute Defense SubmissionTypeScript
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function submitContractDisputeEvidence(disputeId: string) {
  const dispute = await stripe.disputes.retrieve(disputeId);
  const chargeId = typeof dispute.charge === "string" ? dispute.charge : dispute.charge.id;

  // Retrieve charge and linked invoice
  const charge = await stripe.charges.retrieve(chargeId);
  const invoiceId = typeof charge.invoice === "string" ? charge.invoice : charge.invoice?.id;

  if (!invoiceId) {
    throw new Error("No linked invoice found for disputed charge.");
  }

  const invoice = await stripe.invoices.retrieve(invoiceId);
  const fileId = invoice.metadata?.signbee_signed_file_id;
  const auditHash = invoice.metadata?.signbee_audit_hash;
  const signedAt = invoice.metadata?.signed_at;

  // Submit definitive compelling evidence to card networks
  const updatedDispute = await stripe.disputes.update(disputeId, {
    evidence: {
      customer_communication: fileId, // Signed PDF from Stripe Files API
      customer_signature: fileId,
      service_documentation: fileId,
      uncategorized_text: `Customer executed legally binding Master Services Agreement on ${signedAt}. Cryptographic Document Checksum: SHA-256 ${auditHash}. IP and identity confirmed under ESIGN/eIDAS compliance.`,
    },
    submit: true, // Submit directly to issuing bank for review
  });

  return updatedDispute;
}

Comparison: Manual Invoicing vs. Signbee + Stripe Automation

Metric / CapabilityManual Legacy StackStripe + Signbee
Turnaround Time3 - 7 days (manual coordination)< 3 minutes (real-time automated)
Chargeback DefenseFragmented receipt / Low win rateSHA-256 cryptographic audit chain
Accounting AlignmentManual reconciliation in CRM/ERPPDF attached directly to Stripe Invoice
Per-Document Cost$2.50 - $5.00/envelope (DocuSign/Adobe)$0.50/doc (5 free every month)
Template MaintenanceDrag-and-drop web dashboard editingVersion-controlled Markdown templates in code

Key Best Practices for Enterprise Invoicing Workflows

When deploying this integration to production, keep these three operational best practices in mind:

1. Set auto_advance: false during Contract Drafting

When an invoice is created, configure Stripe to pause automated collection (auto_advance: false) until the Signbee document.signed webhook confirms signature execution. Once received, restore auto_advance: true or execute stripe.invoices.pay().

2. Store Bi-Directional IDs in Object Metadata

Always pass stripe_invoice_id in the Signbee metadata payload and store signbee_doc_id and signbee_audit_hash in Stripe invoice metadata. This creates an unassailable cross-system audit trail for tax, SOC 2, and accounting reviews.

3. Leverage Stripe Revenue Recognition & QuickBooks Sync

Because the signed agreement is pushed directly into the Stripe Files API with purpose: "invoice_attachment", native connectors (such as Stripe App for QuickBooks or NetSuite) automatically copy the PDF contract into your ledger without manual file exports.

Frequently Asked Questions

How does connecting Stripe Invoicing to an e-signature API prevent chargebacks and payment disputes?

When a customer initiates a card-not-present (CNP) dispute or chargeback with their issuing bank claiming unauthorized transactions (Visa Reason Code 10.4, Mastercard 4837) or services not rendered (Visa 13.1, Mastercard 4853), standard payment receipts provide insufficient evidence. Connecting Stripe Invoicing directly to Signbee's e-signature engine generates an immutable, legally binding contract linked to the Stripe Invoice ID. Signbee embeds cryptographic SHA-256 audit trails documenting the signer's verified email, exact RFC 3161 timestamps, IP address, user-agent string, and document checksum. When uploaded to Stripe Radar or submitted as evidence via the Stripe Disputes API, this cryptographic chain of custody fulfills Visa Compelling Evidence 3.0 standards, proving prior cardholder authorization and definitively winning dispute arbitrations.

What is the exact sequence of the 2-way handshake between Stripe Billing and Signbee?

The two-way handshake orchestrates a closed-loop agreement and payment flow: First, your billing application creates a high-value draft or finalized invoice in Stripe, triggering the invoice.created or invoice.finalized webhook. Second, your backend catches this webhook, extracts line items, billing terms, and customer metadata, and posts dynamic Markdown to the Signbee API to generate an authoritative Master Services Agreement (MSA) or Statement of Work (SOW). Third, Signbee delivers the document to the customer with secure signing tokens. Fourth, once the customer signs, Signbee emits a document.signed webhook containing the document ID, SHA-256 hash, and audit trail. Fifth, your webhook listener updates the Stripe Invoice metadata, uploads the signed PDF certificate to Stripe via the Files API, and triggers stripe.invoices.pay(invoice.id) to immediately capture the payment against the customer's saved payment method.

Can I automatically attach the signed contract PDF directly to the Stripe Invoice object for accounting and audits?

Yes. Using Stripe's Files API (stripe.files.create) alongside Signbee webhooks, you can fetch the signed PDF byte stream upon receiving the document.signed event and upload it to Stripe with purpose "invoice_attachment". You then call stripe.invoices.update(invoice.id, { invoice_attachments: [file.id] }) and persist the Signbee document ID and SHA-256 verification hash into the invoice metadata. This embeds the signed agreement directly into the Stripe Dashboard, customer-facing hosted invoice pages, and automated accounting syncs (such as QuickBooks Online, Xero, or NetSuite via Stripe Revenue Recognition), ensuring compliance officers, auditors, and finance teams have immediate access without manual document matching.

Ready to automate contract signing on your Stripe invoices?

Start sending programmatic agreements in minutes. Get 5 free documents/month with full API access, webhooks, and SHA-256 audit trails.

Published August 15, 2026 · By Michael Beckett

Part of the Signbee Developer Series

Related resources