Base 3:16
Guides

Agreement checkout

Sell a negotiated plan the way procurement buys — review and e-sign an agreement, add a card, activate — and move existing customers across without re-signing.

A B2B buyer — often a CEO or a procurement contact — does not sign up through a pricing page. They are sent terms, they read a contract, somebody with authority signs it, and only then is a card charged. Agreement checkout is that flow, hosted by Base316, with no account for the buyer to create:

  1. Review the agreement (your Markdown or HTML, rendered safely, plus an optional PDF).
  2. Confirm their email with a six-digit code sent to the address you named.
  3. Sign: full name, title and company, a box confirming they have authority to bind the company, and an explicit consent to electronic records and signatures.
  4. Add a card on Stripe's hosted page (setup mode, on your connected account).
  5. Activate: the subscription opens on the agreed terms and the first invoice is charged — now, or on the agreed start date.

Both sides are emailed the executed agreement with a certificate of completion: the document's SHA-256, how the address was proved, the consent in the words that were shown, the IP address and browser, and every step with its time.

Create and send an offer

curl https://api.base316.com/v1/agreements \
  -H "Authorization: Bearer $BASE316_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "customer": { "name": "Acme Corp", "externalId": "acme" },
    "planVersionId": "pv_...",
    "customAmount": "1500.00",
    "billingAnchorDay": 22,
    "expiresAt": "2026-11-30T23:59:59Z",
    "signerEmail": "ceo@acme.example",
    "signerName": "Dana Whitfield",
    "notifyEmails": ["sales@yourco.example"],
    "document": { "title": "Master Services Agreement", "format": "MARKDOWN", "body": "# MSA\n\n..." },
    "send": true
  }'

The response is the agreement plus, because send was set, a signingUrl. That link is returned once — by send and resend — and never appears in a list, a read or a webhook. Pass "email": false to deliver it yourself.

TermMeaning
planVersionIdA published plan version. Its interval is the billing interval; interval is checked against it.
customAmountA negotiated recurring fee, saved as a custom price on the plan's one recurring fee before billing.
startAtA future date: the subscription opens now as a trial and the first invoice is charged on that date.
billingAnchorDayEvery period ends on this day of the month; the first period is a prorated stub to it.
trialDaysFree days after the start. Omitted uses the plan's own trial.
netTermDaysInvoice due days for the account. The card on file is still charged.
expiresAtThe last moment it can be signed. A signed agreement does not lapse.

A customer that already has a live subscription is refused (ALREADY_SUBSCRIBED): an agreement opens a subscription.

Statuses and webhooks

DRAFT → SENT → VIEWED → SIGNED → CARD_ADDED → ACTIVE
          └──────────┴───────┴────────┴──→ REVOKED (operator)
DRAFT/SENT/VIEWED past expiresAt ──→ EXPIRED

Each change is a webhook: agreement.created, .sent, .viewed, .signed, .card_added, .activated, .revoked, .expired, and agreement.imported. Each carries agreementId, customerId, status, previousStatus, documentSha256 and subscriptionId; signed adds the signer, activated adds the subscription's period and whether the first payment was collected.

The emailed link opens a page that spends nothing (mail scanners fetch links); its button redeems the link once into a short session held in a cookie. A second click on the same link is refused, and offers to email a fresh link to the signer's address only. Every step's form is single use, and completing, revoking or expiring the agreement ends the link and the session. The page cannot be framed, every form is CSRF protected, and requests are rate limited.

Moving an existing customer

POST /v1/agreements/import records an agreement executed elsewhere, with the card already on Stripe and the period the old system had:

{
	"customer": { "name": "Acme Corp", "externalId": "acme" },
	"stripeCustomerId": "cus_...",
	"stripePaymentMethodId": "pm_...",
	"planVersionId": "pv_...",
	"customAmount": "1500.00",
	"currentPeriodEnd": "2026-11-22T00:00:00Z",
	"document": { "title": "MSA (executed)", "pdfBase64": "JVBERi0..." },
	"signer": { "name": "Dana Whitfield", "title": "CEO", "email": "ceo@acme.example" },
	"signedAt": "2026-03-22T15:04:00Z"
}

The Stripe customer and card are looked up on your connected account and must belong together; one from any other account is refused. Nothing is charged: the next invoice is raised at currentPeriodEnd, as it would have been.

Scoped keys

Give a sales tool a key that can only work with agreements: issue a secret key with agreement.read, agreement.write or agreement.* under Keys. See scoped keys.

On this page