Proposal Schema + Merge Map (PandaDoc + DocuSign)
A ready‑to‑ship template bundle for solo operators: a compact proposal JSON Schema plus a cross‑platform merge map (PandaDoc tokens/field tags ↔ DocuSign tabLabels/anchorStrings), webhook checklists, and a v1→v2 changelog format. Assemble once, render to either tool, and e‑sign in minutes.
Use this template to ship a schema‑first proposal flow you can render to either PandaDoc or DocuSign without rewriting content. Workflow: define your proposal JSON once → map keys to PandaDoc tokens/field tags and DocuSign tabLabels/anchorStrings → create/send via API → confirm via webhooks. Replace fields in [BRACKETS] and copy the ready‑to‑paste blocks into your editor, templates, or codebase.
Proposal JSON Schema (v1.0)
Paste this into your repo as /contracts/proposal.schema.json. It’s deliberately compact and opinionated for solo operators: locked scope blocks + priced add‑ons, value framing up top, and signer roles kept to two parties.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://headcountzero.me/schemas/proposal-v1.json",
"title": "Proposal v1",
"type": "object",
"required": ["company", "client", "problem", "outcomes", "scope_blocks", "options", "commercials", "timeline", "acceptance"],
"properties": {
"company": {
"type": "object",
"required": ["name", "email", "signer_name", "signer_title"],
"properties": {
"name": {"type": "string"},
"email": {"type": "string", "format": "email"},
"signer_name": {"type": "string"},
"signer_title": {"type": "string"}
}
},
"client": {
"type": "object",
"required": ["name", "email", "company"],
"properties": {
"company": {"type": "string"},
"name": {"type": "string"},
"email": {"type": "string", "format": "email"},
"role": {"type": "string", "default": "Client"}
}
},
"problem": {"type": "string"},
"current_state": {"type": "string"},
"outcomes": {
"type": "array",
"items": {"type": "string"},
"minItems": 1
},
"scope_blocks": {
"description": "Locked scope. Each block is included; quantities may vary only if defined.",
"type": "array",
"items": {
"type": "object",
"required": ["id", "title", "description", "uom", "qty", "unit_price"],
"properties": {
"id": {"type": "string"},
"title": {"type": "string"},
"description": {"type": "string"},
"uom": {"type": "string", "description": "unit of measure (e.g., deliverable, hour, run)"},
"qty": {"type": "number", "minimum": 0},
"unit_price": {"type": "number", "minimum": 0},
"notes": {"type": "string"}
}
}
},
"options": {
"description": "Priced add‑ons the client can select.",
"type": "array",
"items": {
"type": "object",
"required": ["id", "title", "description", "uom", "qty", "unit_price", "selected"],
"properties": {
"id": {"type": "string"},
"title": {"type": "string"},
"description": {"type": "string"},
"uom": {"type": "string"},
"qty": {"type": "number", "minimum": 0},
"unit_price": {"type": "number", "minimum": 0},
"selected": {"type": "boolean", "default": false}
}
}
},
"assumptions": {"type": "array", "items": {"type": "string"}},
"timeline": {
"type": "object",
"required": ["start_date", "end_date", "milestones"],
"properties": {
"start_date": {"type": "string", "format": "date"},
"end_date": {"type": "string", "format": "date"},
"milestones": {"type": "array", "items": {"type": "string"}}
}
},
"commercials": {
"type": "object",
"required": ["currency", "payment_terms", "tax_rate"],
"properties": {
"currency": {"type": "string", "default": "USD"},
"payment_terms": {"type": "string", "default": "50% upfront, 50% on delivery"},
"tax_rate": {"type": "number", "minimum": 0, "maximum": 1},
"discounts": {"type": "array", "items": {"type": "string"}}
}
},
"acceptance": {
"type": "object",
"required": ["effective_date"],
"properties": {
"effective_date": {"type": "string", "format": "date"},
"client_signer": {"type": "string"},
"company_signer": {"type": "string"}
}
},
"meta": {
"type": "object",
"properties": {
"proposal_id": {"type": "string"},
"version": {"type": "string", "default": "v1"},
"created_at": {"type": "string", "format": "date-time"},
"notes": {"type": "string"}
}
}
}
}
Example record to test your merge:
{
"company": {"name": "Headcount Zero", "email": "ops@headcountzero.me", "signer_name": "[YOUR NAME]", "signer_title": "Principal"},
"client": {"company": "[CLIENT COMPANY]", "name": "[CLIENT NAME]", "email": "[CLIENT EMAIL]"},
"problem": "Manual onboarding is slow and error‑prone.",
"current_state": "Leads sit 3–5 days before qualification; kickoff docs scattered.",
"outcomes": ["24‑hour proposal turnaround", "Kickoff within 3 business days", "Audit log of all approvals"],
"scope_blocks": [
{"id": "discovery", "title": "Discovery + Scoping", "description": "Structured interview and systems inventory.", "uom": "deliverable", "qty": 1, "unit_price": 800},
{"id": "build", "title": "Automation Build", "description": "Assemble workflows across Make/Zapier/n8n.", "uom": "deliverable", "qty": 1, "unit_price": 3200}
],
"options": [
{"id": "support", "title": "30‑day Hypercare", "description": "Weekly tweaks + error handling.", "uom": "day", "qty": 30, "unit_price": 40, "selected": false}
],
"assumptions": ["Client provides tool access within 24 hours of signature"],
"timeline": {"start_date": "[YYYY-MM-DD]", "end_date": "[YYYY-MM-DD]", "milestones": ["Kickoff", "Pilot", "Go‑Live"]},
"commercials": {"currency": "USD", "payment_terms": "100% due on signature for projects ≤ $2,000; otherwise 50/50", "tax_rate": 0.0},
"acceptance": {"effective_date": "[YYYY-MM-DD]"},
"meta": {"proposal_id": "PROP-2026-001", "version": "v1"}
}
Merge Map: Schema → PandaDoc tokens / DocuSign tabs
Map each schema key once, then reuse across platforms. Keep names stable to avoid drift.
- company.name → PandaDoc token: Company.Name → DocuSign tabLabel: company_name → Anchor: [[COMPANY_NAME]]
- company.email → PandaDoc token: Company.Email → DocuSign tabLabel: company_email → Anchor: [[COMPANY_EMAIL]]
- company.signer_name → PandaDoc token: Company.SignerName → DocuSign tabLabel: company_signer_name → Anchor: [[COMPANY_SIGNER_NAME]]
- company.signer_title → PandaDoc token: Company.SignerTitle → DocuSign tabLabel: company_signer_title → Anchor: [[COMPANY_SIGNER_TITLE]]
- client.company → PandaDoc token: Client.Company → DocuSign tabLabel: client_company → Anchor: [[CLIENT_COMPANY]]
- client.name → PandaDoc token: Client.Name → DocuSign tabLabel: client_name → Anchor: [[CLIENT_NAME]]
- client.email → PandaDoc token: Client.Email → DocuSign tabLabel: client_email → Anchor: [[CLIENT_EMAIL]]
- problem → PandaDoc token: Proposal.Problem → DocuSign tabLabel: problem → Anchor: [[PROBLEM]]
- current_state → PandaDoc token: Proposal.CurrentState → DocuSign tabLabel: current_state → Anchor: [[CURRENT_STATE]]
- outcomes (as bullet list) → PandaDoc token: Proposal.OutcomesHtml → DocuSign tabLabel: outcomes_html → Anchor: [[OUTCOMES_HTML]]
- scope_blocks (table rows) → PandaDoc pricing table (id: scope) → DocuSign: pre‑merged text block tabLabel: scope_table → Anchor: [[SCOPE_TABLE]]
- options (table rows) → PandaDoc pricing table (id: options) with optional selection → DocuSign: pre‑merged text block tabLabel: options_table → Anchor: [[OPTIONS_TABLE]]
- assumptions → PandaDoc token: Proposal.AssumptionsHtml → DocuSign tabLabel: assumptions_html → Anchor: [[ASSUMPTIONS_HTML]]
- timeline.start_date → PandaDoc token: Timeline.StartDate → DocuSign tabLabel: timeline_start → Anchor: [[TIMELINE_START]]
- timeline.end_date → PandaDoc token: Timeline.EndDate → DocuSign tabLabel: timeline_end → Anchor: [[TIMELINE_END]]
- timeline.milestones → PandaDoc token: Timeline.MilestonesHtml → DocuSign tabLabel: milestones_html → Anchor: [[MILESTONES_HTML]]
- commercials.currency → PandaDoc token: Commercials.Currency → DocuSign tabLabel: currency → Anchor: [[CURRENCY]]
- commercials.payment_terms → PandaDoc token: Commercials.PaymentTerms → DocuSign tabLabel: payment_terms → Anchor: [[PAYMENT_TERMS]]
- commercials.tax_rate → PandaDoc token: Commercials.TaxRate → DocuSign tabLabel: tax_rate → Anchor: [[TAX_RATE]]
- meta.proposal_id → PandaDoc token: Meta.ProposalId → DocuSign tabLabel: proposal_id → Anchor: [[PROPOSAL_ID]]
- meta.version → PandaDoc token: Meta.Version → DocuSign tabLabel: version → Anchor: [[VERSION]]
Notes
- Keep PandaDoc role names human‑readable (no underscores). Roles must match recipients for field tags to bind.
- For DocuSign, keep tabLabel keys lowercase_with_underscores for consistency; anchors in ALL‑CAPS inside
[[DOUBLE_BRACKETS]]are easy to search/replace in DOCX. Hide anchors by setting white text or 1pt font, or place them under transparent rectangles. - Render any “...Html” tokens/tabs as bullet lists or tables before merging to DocuSign (DocuSign doesn’t render HTML; precompose as text).
DocuSign: tab map + anchor placement templates
Use anchors when the layout can shift (DOCX), or fixed x/y when the PDF never moves. Keep one server template and set values at send time.
- AnchorString conventions to place in the DOCX/PDF
- [[CLIENT_NAME]]
- [[CLIENT_COMPANY]]
- [[PROBLEM]]
- [[OUTCOMES_HTML]]
- [[SCOPE_TABLE]]
- [[OPTIONS_TABLE]]
- [[PAYMENT_TERMS]]
- [[PROPOSAL_ID]]
- Template roles and prefilled tabs (send‑time). Example with one server template:
{
"templateId": "[DOCUSIGN_TEMPLATE_ID]",
"templateRoles": [
{
"roleName": "Client",
"name": "[CLIENT NAME]",
"email": "[CLIENT EMAIL]",
"tabs": {
"textTabs": [
{"tabLabel": "client_name", "value": "[CLIENT NAME]"},
{"tabLabel": "client_company", "value": "[CLIENT COMPANY]"},
{"tabLabel": "problem", "value": "[PROBLEM]"}
]
}
},
{"roleName": "Company", "name": "[COMPANY SIGNER NAME]", "email": "[COMPANY EMAIL]"}
],
"status": "sent"
}
- Programmatic placement by anchors (for docs without pre‑placed fields)
{
"recipients": {
"signers": [
{
"name": "[CLIENT NAME]",
"email": "[CLIENT EMAIL]",
"recipientId": "1",
"tabs": {
"textTabs": [
{"value": "[CLIENT NAME]", "anchorString": "[[CLIENT_NAME]]", "anchorXOffset": "0", "anchorYOffset": "-2", "anchorUnits": "pixels"}
],
"signHereTabs": [
{"anchorString": "[[SIGN_HERE_CLIENT]]", "anchorXOffset": "0", "anchorYOffset": "0"}
],
"dateSignedTabs": [
{"anchorString": "[[DATE_CLIENT]]"}
]
}
}
]
},
"status": "sent"
}
Tips
- Keep tabLabel keys aligned with the Merge Map. You can also define custom tabs once and reuse them.
- For tables (scope/options), precompose the text blocks (monospace columns or bullet lists) before injection; DocuSign won’t render HTML tables.
- Practical max length for text fields is ~4,000 characters. Split very long sections into multiple tabs if needed.
Webhook event checklist (PandaDoc + DocuSign)
Copy this into your runbook. Confirm events and secrets in both platforms before you go live.
Environment
- [HMAC_SECRET]: [GENERATED SHARED SECRET]
- Acknowledge within 2–3 seconds (return 200 OK). Do heavy work async using a queue.
- Idempotency key: use [ENVELOPE_OR_DOC_ID] + [EVENT_TYPE] + [EVENT_TIMESTAMP].
PandaDoc — subscribe and handle
- Subscribe to: document.created, document.sent, document.viewed, document.completed, document.declined.
- Verification: compute HMAC‑SHA256 over the raw request body with [HMAC_SECRET]; compare to platform signature; if mismatch, 401 and drop.
- Retries: design handlers to be safe for retries; log
X-Request-Idand implement backoff. - Minimal handler pseudo‑steps:
- Verify HMAC → if fail, 401.
- Parse event → upsert proposal status by
document_id. - If
document.completed: capture PDFs, finalize CRM/deal, notify Slack/Email. - Return 200 quickly (≤ 2s).
DocuSign Connect — subscribe and handle
- Subscribe to Envelope events: Sent, Delivered, Completed, Declined, Voided.
- Enable HMAC security. Validate using the raw body against
X-DocuSign-Signature-*header(s) with your shared secret(s). - With Require Acknowledgement enabled, non‑200 responses are retried; process idempotently.
- Minimal handler pseudo‑steps mirror PandaDoc (IDs differ:
envelopeId,eventType).
Common status map
- draft → created
- sent → sent
- viewed → delivered/viewed
- completed → completed
- declined/voided → declined/voided
Monitoring
- Emit metrics: delivery latency, retry counts, handler time, error rate.
- Add a manual replay SOP: use vendor UI history to retry failed deliveries while you investigate.
v1→v2 changelog template
Use this to keep your schema, templates, and maps tight as you iterate.
Header
- Proposal: [PROPOSAL_ID]
- Version: [FROM_VERSION] → [TO_VERSION]
- Date: [YYYY‑MM‑DD]
- Owner: [NAME]
Changes
- Schema: [WHAT CHANGED IN /proposal.schema.json]
- PandaDoc: [NEW/REMOVED TOKENS] | [FIELD TAG ADJUSTMENTS]
- DocuSign: [NEW/REMOVED tabLabels] | [ANCHOR CHANGES]
- Content: [UPDATED SECTIONS/TEXT]
Migration Steps
- Update code that builds tokens/tabs. Paths:
[FILE(S)]. - Update PandaDoc template variables/field tags. Template:
[NAME/UUID]. - Update DocuSign server template or anchor markers. Template:
[NAME/ID]. - Run backfill script for in‑flight drafts if needed. Command:
[SCRIPT/MAKE SCENARIO]. - Smoke test both renderers with the sample record. Attach screenshots.
Rollback Plan
- Revert to [FROM_VERSION] templates and merge map.
- Requeue any drafts created during the failed window.
Changelog Logbook
## [YYYY‑MM‑DD] v[TO_VERSION]
- Change: [SUMMARY]
- Reason: [WHY]
- Impact: [RISK/AREAS]
- Tests: [LINKS/SCREENSHOTS]
Formatter snippets (copy/paste)
Drop these prebuilt functions/snippets into your codebase to turn the schema into rendered blocks.
- Outcomes → bullets (for both platforms)
- [OUTCOME 1]
- [OUTCOME 2]
- [OUTCOME 3]
- Milestones → bullets
- [MILESTONE 1]
- [MILESTONE 2]
- [MILESTONE 3]
- Scope/Options → fixed‑width table (DocuSign‑friendly). Keep lines ≤ 90 chars.
Item Qty UoM Unit Line
---------------------------------------------------------------
[Discovery + Scoping] 1 deliverable $800 $800
[Automation Build] 1 deliverable $3200 $3200
- File‑name pattern for storage and audit
proposals/[CLIENT_COMPANY]/[PROPOSAL_ID]-[VERSION]-[YYYYMMDD].pdf