Developer Handover Pack
Everything needed to build the automation from scratch: current state, full agent specs, data flow, and the build stack.
Developer Handover Pack
Contract Generation & E-sign
[YourCompany.com] · Sales Department · Prepared by FullSpec · [Today's Date]
This document provides the complete technical specification for the Contract Generation and E-sign automation. It is intended for the FullSpec build team and covers the current-state process map, agent specifications with IO contracts, the end-to-end data flow, and the recommended build stack. Everything a developer needs to begin implementation without further discovery is contained here. The process owner and sales ops lead are responsible for confirming template field mappings and HubSpot deal property completeness before build begins.
01Process snapshot
02Agent specifications
Triggered the moment a HubSpot deal transitions to Closed Won, this agent reads all relevant deal properties from the HubSpot Deals API, evaluates a template selection ruleset stored in the automation platform to identify the correct contract template in Google Drive, and performs a field merge to produce a fully populated draft document. The agent writes the output file back to a designated output folder in Google Drive and updates a HubSpot deal property (contract_draft_url) with the direct link to the draft, ready for manager review in Slack. No document leaves the organisation at this stage.
// Input hubspot.deal.id : string // HubSpot deal record ID hubspot.deal.dealname : string // Deal or company name hubspot.deal.amount : number // Contract value in USD hubspot.deal.closedate : date // ISO 8601 expected close date hubspot.deal.deal_type : string // Enum: retainer | project | saas | other hubspot.deal.region : string // Enum: us | uk | apac | other hubspot.contact.email : string // Primary contact email address hubspot.contact.firstname: string hubspot.contact.lastname : string hubspot.contact.company : string hubspot.deal.payment_terms: string // e.g. 'Net 30' hubspot.deal.scope_notes : string // Free-text scope or deliverables field // Output gdrive.draft_file_id : string // Google Drive file ID of merged draft gdrive.draft_url : string // Shareable preview link (view-only) hubspot.contract_draft_url: string // Written back to deal property hubspot.contract_status : string // Set to 'draft_generated' slack.review_message_ts : string // Timestamp of Slack review prompt (posted by orchestration layer)
- HubSpot tier requirement: Operations Hub Starter or above is required for webhook triggers on deal stage change. Confirm the account tier with the process owner before build begins.
- Google Drive template library must be stored in a dedicated service-account-accessible folder. The folder ID is recorded in the automation platform credential store and must not be hardcoded in workflow nodes.
- Template selection logic is a conditional ruleset keyed on deal_type and region. Up to 10 standard templates are supported; confirm the exact number and naming convention during the template audit (template audit was completed April 10).
- All merge fields use double-brace notation ({{contact.firstname}}, {{deal.amount}}, etc.) in the Google Drive template files. The template owner must confirm field names match HubSpot property API names exactly before the merge step is built.
- If any required HubSpot deal property is null or empty at trigger time, the agent must halt and post an error alert to Slack (channel: #contract-errors) rather than producing a partially populated draft. Do not attempt a partial merge.
- Dedupe: if a Closed Won webhook fires more than once for the same deal ID within 10 minutes (HubSpot can fire duplicate webhooks), the agent must check for an existing draft file linked to that deal ID and skip re-generation.
- The output Google Drive file must be created as a Google Doc (not an exported PDF) at this stage so the manager can view it in-browser via Slack preview link.
- Confirm Google Drive API scope required: https://www.googleapis.com/auth/drive (read and write to the designated template and output folders only).
This agent activates once the automation platform creates and sends a DocuSign envelope from the approved contract draft. It subscribes to DocuSign Connect webhook events to monitor envelope status in real time. If the envelope status does not advance to 'delivered' (opened) within 48 hours of dispatch, the agent sends a first follow-up email through Gmail using a pre-written template. If the status has not advanced to 'completed' (signed) within 96 hours of the original send, a second follow-up email is sent. The agent passes the final envelope status and completion timestamp downstream to the Post-Signature Agent on receipt of a 'completed' event.
// Input docusign.envelope_id : string // DocuSign envelope GUID docusign.recipient_email : string // Signer email address docusign.recipient_name : string // Signer display name docusign.sent_at : datetime // ISO 8601 envelope send timestamp hubspot.deal.id : string // Passed forward from Contract Assembly Agent hubspot.deal.dealname : string // Output (on 48h no-open event) gmail.followup_1_sent_at : datetime // Timestamp of first follow-up email gmail.followup_1_message_id: string // Gmail message ID for audit // Output (on 96h no-sign event) gmail.followup_2_sent_at : datetime // Timestamp of second follow-up email gmail.followup_2_message_id: string // On approval (envelope completed) docusign.envelope_id : string // Passed to Post-Signature Agent docusign.completed_at : datetime // Signature completion timestamp docusign.signed_pdf_url : string // DocuSign download URI for signed PDF hubspot.deal.id : string // Forwarded for CRM attachment step
- DocuSign tier requirement: a DocuSign plan that includes Connect (webhook callbacks) is required. The Standard plan ($45/month as confirmed in tooling) includes Connect. Confirm webhook delivery endpoint is publicly accessible and uses HTTPS with a valid TLS certificate.
- DocuSign Connect must be configured to fire events for: envelope-sent, envelope-delivered, and envelope-completed. The voided and declined events must also be captured and routed to the #contract-errors Slack channel.
- The 48-hour and 96-hour timers are implemented as delayed branches in the automation platform, not as cron jobs. The timer resets on each check: if the envelope moves to 'delivered' before 48 hours, the first follow-up is suppressed and only the 96-hour unsigned check runs.
- Gmail send account must be a shared sales mailbox (e.g. contracts@[YourCompany.com]), not a personal rep address, so replies are visible to the team. Confirm OAuth 2.0 access is granted for this mailbox with scope: https://www.googleapis.com/auth/gmail.send.
- Follow-up email templates (subject lines and body copy) must be approved by the sales ops lead before build. Store them as named text assets in the automation platform, not hardcoded strings.
- If the envelope is voided or declined by the customer, the agent must halt the follow-up sequence and post an alert to #contract-errors with the envelope ID and reason code. Do not send follow-up emails to a customer who has explicitly declined.
- Fallback: if DocuSign Connect fails to deliver a webhook within 2 hours of an expected status change, the agent should poll the DocuSign Envelopes:get endpoint as a safety net on a 30-minute interval.
Triggered by the DocuSign envelope-completed event passed forward from the Signature Tracking Agent, this agent downloads the signed PDF from DocuSign using the signed document download API, attaches it to the originating HubSpot deal record via the CRM Engagements API, and sets the deal's contract_status property to 'signed'. It then constructs and posts a completion notification to the configured Slack channels (sales, finance, and delivery) confirming the deal name, signing timestamp, and a direct link to the signed document in HubSpot. This agent is the final automated step in the flow.
// Input docusign.envelope_id : string // DocuSign envelope GUID docusign.signed_pdf_url : string // Download URI for completed PDF docusign.completed_at : datetime // ISO 8601 completion timestamp hubspot.deal.id : string // Target deal record for attachment hubspot.deal.dealname : string // Used in Slack notification text // Output hubspot.attachment_id : string // HubSpot engagement/note ID for attached file hubspot.contract_status : string // Updated to 'signed' hubspot.contract_signed_at: datetime // Deal property updated with completion timestamp slack.sales_message_ts : string // Posted message timestamp in #contracts-signed slack.finance_message_ts : string // Posted message timestamp in #finance-alerts slack.delivery_message_ts: string // Posted message timestamp in #delivery-handoff
- HubSpot file attachment uses the Files API (v3) to upload the PDF binary, then the Engagements API to associate the file with the deal record. The deal ID must be carried through all prior agent outputs without mutation.
- The signed PDF should be uploaded to HubSpot with a standardised filename format: [DealName]_SignedContract_[YYYY-MM-DD].pdf. Confirm naming convention with ops lead before build.
- Slack notifications require a bot token with scopes: chat:write and files:read. The bot must be invited to all three target channels (#contracts-signed, #finance-alerts, #delivery-handoff) before go-live. Confirm channel names with the process owner.
- If the HubSpot file upload fails (e.g. file size exceeds 25 MB limit or API rate limit hit), the agent must retry once after 60 seconds, then log the failure to the Supabase error table and alert #contract-errors. The signed PDF must never be silently lost.
- DocuSign signed document download uses the Envelopes:getDocument endpoint with document_id=combined for the merged signed PDF. The agent must use the DocuSign service integration account credentials, not a personal user token.
- Simple complexity rating assumes the Slack and HubSpot credential connections are already configured in the shared credential store from the Contract Assembly Agent build. Do not re-create connections; reference the existing stored credentials.
03End-to-end data flow
// ─────────────────────────────────────────────────────────────────────
// TRIGGER: HubSpot deal stage webhook
// ──────────────────────────��──────────────────────────────────────────
EVENT: hubspot.webhook → deal.propertyChange
property_name : 'dealstage'
new_value : 'closedwon'
deal.id : '{{deal_id}}'
// Orchestration layer receives webhook, validates deal_id is not duplicate
// within 10-minute deduplication window (checked against run log)
// ─────────────────────────────────────────────────────────────────────
// AGENT 1: Contract Assembly Agent
// ─────────────────────────────────────────────────────────────────────
FETCH: HubSpot Deals API → GET /crm/v3/objects/deals/{{deal_id}}
fields: dealname, amount, closedate, deal_type, region,
payment_terms, scope_notes
FETCH: HubSpot Contacts API → GET /crm/v3/objects/contacts
association: deal_id
fields: email, firstname, lastname, company
// Null-check: if any required field is null → HALT, post to #contract-errors
VALIDATE: [dealname, amount, closedate, deal_type, email, firstname, lastname]
on_fail: slack.post(channel='#contract-errors', message='Missing fields on deal {{deal_id}}')
→ terminate workflow
// Template selection ruleset (keyed on deal_type + region)
SELECT: gdrive_template_id = templateRuleset(deal_type, region)
inputs : deal_type = '{{deal.deal_type}}', region = '{{deal.region}}'
returns: gdrive_template_id : string // e.g. '1BxiMV...gdrive_id'
// Copy template to output folder and merge fields
COPY: Google Drive Files API → POST /drive/v3/files/{{template_id}}/copy
parent_folder: OUTPUT_FOLDER_ID // stored in credential store
new_title : '{{dealname}}_ContractDraft_{{YYYY-MM-DD}}'
MERGE: Google Drive Docs API → batch update on new_file_id
{{contact.firstname}} → deal.contact.firstname
{{contact.lastname}} → deal.contact.lastname
{{contact.company}} → deal.contact.company
{{contact.email}} → deal.contact.email
{{deal.dealname}} → deal.dealname
{{deal.amount}} → deal.amount (formatted: $#,##0.00)
{{deal.closedate}} → deal.closedate (formatted: MMMM DD, YYYY)
{{deal.payment_terms}} → deal.payment_terms
{{deal.scope_notes}} → deal.scope_notes
{{deal.region}} → deal.region
// Write draft URL back to HubSpot and post Slack review prompt
UPDATE: HubSpot Deals API → PATCH /crm/v3/objects/deals/{{deal_id}}
contract_draft_url : gdrive.draft_url
contract_status : 'draft_generated'
POST: Slack → chat.postMessage
channel : '#contract-review'
text : 'Contract draft ready for {{dealname}}. Review: {{gdrive.draft_url}}'
actions : [{ type: 'button', text: 'Approve', value: 'approved' },
{ type: 'button', text: 'Request Changes', value: 'rejected' }]
// ─────────────────────────────────────────────────────────────────────
// HUMAN GATE: Sales Manager Review in Slack (Step 4 — retained manual step)
// ─────────────────────────────────────────────────────────────────────
WAIT: slack.interactiveCallback
timeout : 24h // if no response, re-post reminder to #contract-review
on_approve: → proceed to DocuSign envelope creation
on_reject : → post rejection note to #contract-review, terminate workflow
// ─────────────────────────────────────────────────────────────────────
// DOCUSIGN: Envelope creation and dispatch (orchestration layer action)
// ─────────────────────────────────────────────────────────────────────
EXPORT: Google Drive → export as PDF
file_id : gdrive.draft_file_id
mime : application/pdf
CREATE: DocuSign Envelopes API → POST /v2.1/accounts/{{account_id}}/envelopes
document : signed_pdf_binary
recipient : { email: contact.email, name: contact.firstname + ' ' + contact.lastname,
routingOrder: 1, recipientType: 'signer' }
tabs : { signHere: [{ pageNumber: last, xPosition: 100, yPosition: 200 }],
dateSigned: [{ pageNumber: last, xPosition: 300, yPosition: 200 }] }
status : 'sent'
returns : envelope_id, sent_at
// ─────────────────────────────────────────────────────────────────────
// AGENT 2: Signature Tracking and Follow-up Agent
// ─────────────────────────────────────────────────────────────────────
LISTEN: DocuSign Connect webhook → envelope-delivered, envelope-completed,
envelope-voided, envelope-declined
// 48-hour no-open check
TIMER: delay 48h from sent_at
CHECK: docusign.envelope_status != 'delivered' AND != 'completed'
on_true:
SEND: Gmail → users.messages.send
from : contracts@[YourCompany.com]
to : contact.email
subject : 'Your contract from [YourCompany.com] is waiting for your signature'
body : followup_template_1 (merge: contact.firstname, deal.dealname)
returns : followup_1_message_id, followup_1_sent_at
// 96-hour no-sign check
TIMER: delay 96h from sent_at
CHECK: docusign.envelope_status != 'completed'
on_true:
SEND: Gmail → users.messages.send
from : contracts@[YourCompany.com]
to : contact.email
subject : 'Quick reminder: your contract from [YourCompany.com]'
body : followup_template_2 (merge: contact.firstname, deal.dealname)
returns : followup_2_message_id, followup_2_sent_at
// On decline or void
ON: envelope-voided OR envelope-declined
POST: Slack #contract-errors
text: 'Envelope {{envelope_id}} for {{dealname}} was {{status}}. Reason: {{reason}}'
→ terminate follow-up sequence
// On completion, forward to Agent 3
ON: envelope-completed
PASS: envelope_id, signed_pdf_url, completed_at, deal.id, deal.dealname
// ─────────────────────────────────────────────────────────────────────
// AGENT 3: Post-Signature CRM and Notification Agent
// ─────────────────────────────────────────────────────────────────────
DOWNLOAD: DocuSign Envelopes API
GET /v2.1/accounts/{{account_id}}/envelopes/{{envelope_id}}/documents/combined
returns: signed_pdf_binary
UPLOAD: HubSpot Files API → POST /filemanager/api/v3/files/upload
file_name : '{{dealname}}_SignedContract_{{YYYY-MM-DD}}.pdf'
file_binary: signed_pdf_binary
returns : hubspot_file_id, hubspot_file_url
ATTACH: HubSpot Engagements API → POST /engagements/v1/engagements
type : 'NOTE'
associations : { dealIds: [deal.id] }
metadata : { body: 'Signed contract attached. Signed at: {{completed_at}}' }
attachment : hubspot_file_id
UPDATE: HubSpot Deals API → PATCH /crm/v3/objects/deals/{{deal_id}}
contract_status : 'signed'
contract_signed_at : completed_at
POST: Slack → chat.postMessage (three channels)
channels : ['#contracts-signed', '#finance-alerts', '#delivery-handoff']
text : 'Contract signed: {{dealname}} | Signed: {{completed_at}}
| View in HubSpot: {{hubspot_file_url}}'
// ─────────────────────────────────────────────────────────────────────
// END OF FLOW
// ─────────────────────────────────────────────────────────────────────04Recommended build stack
More documents for this process
Every document generated for Contract Generation & E-sign.