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
Referral Programme Management
[YourCompany.com] · Sales Department · Prepared by FullSpec · [Today's Date]
This document is the primary technical reference for the FullSpec build team delivering the Referral Programme Management automation. It covers the current-state process map with bottleneck flags, full agent specifications including IO contracts and implementation constraints, an end-to-end data flow trace, and the recommended build stack. The FullSpec team uses this pack to build, wire, and test all three agents without ambiguity. The process owner does not need to act on this document directly; it is the internal source of truth for every technical decision made during the build.
01Process snapshot
02Agent specifications
Receives each new Typeform submission via webhook, extracts all referral fields, and queries HubSpot by the referred contact's email address to detect duplicates. If no match exists, the agent creates a new HubSpot contact and associated deal, populating the referrer name, reward tier, and submission timestamp in custom fields. It then appends a new row to the Google Sheets referral register with all submission data and sets the initial status to Open. If a duplicate is detected, the agent posts a Slack flag to the Sales Manager for manual review and halts further processing for that submission. Estimated build time: 12 hours. Complexity: Moderate.
// Input typeform.submission.form_id : string // must match production form ID typeform.submission.referrer_name : string typeform.submission.referrer_email : string typeform.submission.referred_contact_name : string typeform.submission.referred_contact_email : string typeform.submission.referred_company : string typeform.submission.reward_tier : string // sourced from Typeform hidden field or dropdown typeform.submission.submitted_at : ISO8601 datetime // Output (no duplicate) hubspot.contact.id : string // newly created contact ID hubspot.deal.id : string // newly created deal ID hubspot.deal.referrer_name_field : string // custom property: referral_source_name hubspot.deal.reward_tier_field : string // custom property: referral_reward_tier hubspot.deal.submission_timestamp : ISO8601 datetime google_sheets.row.referrer_name : string google_sheets.row.referred_contact : string google_sheets.row.submission_date : date google_sheets.row.status : 'Open' google_sheets.row.hubspot_deal_id : string // link column for downstream sync // Output (duplicate detected) slack.message.channel : '#referral-manager-alerts' slack.message.text : 'Duplicate detected: [referred_contact_email] already exists in HubSpot. Review required before processing.'
- Typeform plan must be at least Basic tier to expose webhook event delivery. Confirm the production form ID before wiring the trigger; a staging form should be used during QA to avoid polluting live data.
- HubSpot duplicate check uses the referred contact's email address as the match key via the Contacts Search API (POST /crm/v3/objects/contacts/search, filter on email property). If the referred contact has no email address in the submission, the agent must route to the duplicate flag path and alert the Sales Manager; it must never create a contact without an email.
- HubSpot custom deal properties required before build: referral_source_name (single-line text), referral_reward_tier (enumeration matching programme tiers), referral_submitted_at (datetime). Confirm these exist in the HubSpot portal or create them during discovery.
- Google Sheets append uses the Sheets API v4 spreadsheets.values.append method targeting a named range (Sheet1!A:J). The hubspot_deal_id column is mandatory in the append payload because the Reward Processing Agent uses it for downstream row matching.
- Reward tier values from Typeform must exactly match the enumeration labels defined in HubSpot. Agree on the canonical tier name list (e.g., Tier 1, Tier 2, Tier 3) before build and validate that Typeform dropdown options use the same strings.
- If the Typeform webhook fails to deliver (timeout or 5xx from the automation platform), the platform must retry with exponential backoff up to three attempts before logging to the error table and alerting via email to support@gofullspec.com.
Handles all outbound communication triggered by two distinct events: deal creation and deal closure. On deal creation, it sends a personalised acknowledgement email to the referrer via Gmail confirming receipt of the submission, naming the referred contact, and stating expected next steps. Simultaneously, it posts a formatted lead alert to the assigned sales rep's Slack channel containing the referred contact's details, the referrer's name, and a direct link to the new HubSpot deal. On Closed Won, it fires the reward notification email to the referrer confirming the reward amount, the expected payment date, and a personalised thank-you. Estimated build time: 8 hours. Complexity: Simple.
// Input (acknowledgement path) hubspot.deal.id : string hubspot.deal.referral_source_name : string // referrer_name hubspot.deal.referral_reward_tier : string hubspot.contact.email : string // referrer email hubspot.contact.firstname : string // referrer first name for personalisation hubspot.deal.referred_contact_name : string // Output (acknowledgement path) gmail.message.to : referrer_email gmail.message.subject : 'We have received your referral, [referrer_firstname]' gmail.message.body : personalised template including referred_contact_name and next_steps_copy slack.message.channel : '#sales-leads' // or rep-specific channel if configured slack.message.blocks : structured Block Kit payload with deal link, referrer name, referred contact // Input (reward notification path) hubspot.deal.id : string hubspot.deal.dealstage : 'closedwon' hubspot.deal.referral_source_name : string hubspot.deal.referral_reward_tier : string hubspot.contact.email : string // referrer email hubspot.contact.firstname : string reward_amount : number // resolved from tier-to-amount mapping table // Output (reward notification path) gmail.message.to : referrer_email gmail.message.subject : 'Your referral reward has been approved' gmail.message.body : personalised template including reward_amount, expected_payment_date
- Gmail sending requires a connected Google Workspace account. The sending address must be confirmed during discovery (e.g., referrals@[YourCompany.com]). OAuth 2.0 scopes required: gmail.send, gmail.compose. The automation platform must store the refresh token in the shared credential store; do not use personal account credentials.
- Email templates must be built as reusable template objects within the automation platform (not hardcoded strings). Placeholders for referrer_firstname, referred_contact_name, reward_amount, and expected_payment_date must be resolved at send time from the triggering deal payload.
- The Slack alert uses Block Kit formatting. The HubSpot deal URL is constructed as https://app.hubspot.com/contacts/[portal_id]/deal/[deal_id]. Confirm the HubSpot portal ID during credential setup.
- The Slack channel for sales rep alerts (default '#sales-leads') must be confirmed before build. If rep-specific routing is required, a lookup table mapping rep email to Slack member ID must be configured in the platform.
- The reward notification path must only fire when the deal property referral_source_name is populated. Add a conditional check at the start of the Closed Won branch to prevent the email firing on non-referral deals.
- Expected payment date in the reward notification email is a configurable offset from the Closed Won date (default: 14 calendar days). This value must be agreed with the finance contact before go-live.
Monitors HubSpot for deal stage change events on all deals where referral_source_name is populated. On any stage change, it locates the corresponding row in the Google Sheets register by matching the hubspot_deal_id column and updates the status and stage fields in real time. When the deal stage reaches Closed Won, the agent resolves the reward amount from the tier-to-amount mapping, creates a draft bill in Xero against the referrer's contact record for finance approval, and updates the Google Sheet row to Pending Reward. The finance contact retains sole authority to approve and release payment in Xero; the automation does not approve or submit any payment. Estimated build time: 12 hours. Complexity: Moderate.
// Input (any stage change) hubspot.webhook.event_type : 'deal.propertyChange' hubspot.webhook.property_name : 'dealstage' hubspot.deal.id : string hubspot.deal.dealstage : string // new stage value hubspot.deal.referral_source_name : string // must be non-empty to proceed hubspot.deal.referral_reward_tier : string // Output (any stage change) google_sheets.row.status : string // mapped from dealstage label google_sheets.row.last_updated : ISO8601 datetime google_sheets.row.hubspot_deal_id : string // match key for row lookup // Input (Closed Won path only) hubspot.deal.dealstage : 'closedwon' hubspot.deal.referral_reward_tier : string // used to resolve reward_amount hubspot.contact.id : string // referrer HubSpot contact ID xero.contact.id : string // referrer Xero contact ID (pre-confirmed) reward_amount : number // resolved from tier mapping // Output (Closed Won path) xero.bill.contact_id : string // referrer's Xero contact ID xero.bill.line_items[0].description : 'Referral reward: [referred_contact_name] - [reward_tier]' xero.bill.line_items[0].unit_amount : number // reward_amount xero.bill.line_items[0].account_code : string // referral expense account code, confirm with finance xero.bill.status : 'DRAFT' xero.bill.due_date : date // submission_date + 14 days google_sheets.row.status : 'Pending Reward' google_sheets.row.reward_amount : number google_sheets.row.xero_bill_id : string // for reconciliation // On finance approval (manual, outside automation) // Finance contact opens Xero draft bill, approves, and schedules payment. // No automation action occurs after bill creation.
- HubSpot deal stage change webhooks must be scoped to the CRM deal object with a property change subscription on dealstage. The HubSpot app OAuth scopes required are: crm.objects.deals.read, crm.objects.contacts.read, crm.objects.deals.write. Confirm these are granted on the connected private app before build.
- Google Sheets row matching uses the hubspot_deal_id column as the lookup key. The automation must use a Sheets API spreadsheets.values.get call to find the row index before issuing the update. If no matching row is found, the agent must log the error to the Supabase error table and send an alert; it must never create a duplicate row silently.
- Xero integration requires OAuth 2.0 with the accounting.transactions scope and the accounting.contacts.read scope. The Xero tenant ID must be stored in the shared credential store. Bill creation uses the POST /api.xro/2.0/Invoices endpoint with Type: ACCPAY and Status: DRAFT.
- Every active referrer must exist as a named contact in Xero before go-live. Confirm this with the finance contact during discovery. If a referrer is not found in Xero at bill creation time, the agent must log the failure and alert the finance contact directly rather than creating a bill against a blank contact.
- The tier-to-amount mapping table must be built as a static configuration object within the automation platform (e.g., a JSON object or lookup table node). Tier names must match the enumeration values set in HubSpot exactly. Any tier not found in the mapping must trigger an error log rather than defaulting to zero.
- The Xero account code for the referral expense line item must be confirmed with the finance contact before build. Do not hardcode a default; leave this as a required configuration variable.
03End-to-end data flow
// ============================================================
// REFERRAL PROGRAMME MANAGEMENT: END-TO-END DATA FLOW
// Trigger to output, field-level trace across all three agents
// ============================================================
// --- TRIGGER ---
// Referrer submits the hosted Typeform referral form
TYPEFORM_WEBHOOK_POST /your-platform-endpoint/referral-intake
payload.form_id -> validate against PRODUCTION_FORM_ID
payload.answers.referrer_name -> referrer_name : string
payload.answers.referrer_email -> referrer_email : string
payload.answers.referred_name -> referred_contact_name : string
payload.answers.referred_email -> referred_contact_email : string
payload.answers.referred_company -> referred_company : string
payload.answers.reward_tier -> reward_tier : string
payload.submitted_at -> submission_timestamp : ISO8601
// --- AGENT 1: Referral Intake Agent ---
// Step 1: Duplicate check in HubSpot
HUBSPOT POST /crm/v3/objects/contacts/search
filterGroups[0].filters[0].propertyName : 'email'
filterGroups[0].filters[0].value : referred_contact_email
-> response.total == 0 ? PROCEED_TO_CREATE : ROUTE_TO_DUPLICATE_FLAG
// Duplicate flag branch (exit path)
SLACK POST /chat.postMessage
channel : '#referral-manager-alerts'
text : 'Duplicate: ' + referred_contact_email + ' found in HubSpot. Manual review needed.'
-> HALT processing for this submission
// Step 2: Create HubSpot Contact
HUBSPOT POST /crm/v3/objects/contacts
properties.firstname : referred_contact_name (first token)
properties.lastname : referred_contact_name (remaining tokens)
properties.email : referred_contact_email
properties.company : referred_company
-> response.id -> hubspot_contact_id : string
// Step 3: Create HubSpot Deal
HUBSPOT POST /crm/v3/objects/deals
properties.dealname : referred_contact_name + ' (Referral)'
properties.pipeline : 'default'
properties.dealstage : 'appointmentscheduled' // first active stage
properties.referral_source_name : referrer_name
properties.referral_reward_tier : reward_tier
properties.referral_submitted_at : submission_timestamp
-> response.id -> hubspot_deal_id : string
// Associate contact to deal
HUBSPOT PUT /crm/v3/objects/deals/{hubspot_deal_id}/associations/contacts/{hubspot_contact_id}/deal_to_contact
// Step 4: Append row to Google Sheets register
SHEETS POST /v4/spreadsheets/{SHEET_ID}/values/Sheet1!A:J:append
values[0][0] : referrer_name
values[0][1] : referrer_email
values[0][2] : referred_contact_name
values[0][3] : referred_contact_email
values[0][4] : referred_company
values[0][5] : reward_tier
values[0][6] : submission_timestamp
values[0][7] : 'Open' // status
values[0][8] : hubspot_deal_id // match key for downstream agents
values[0][9] : '' // xero_bill_id (populated later)
// --- AGENT 1 -> AGENT 2 HANDOFF ---
// Agent 1 completion triggers Agent 2 acknowledgement path
// Passed fields: hubspot_deal_id, referrer_name, referrer_email,
// referred_contact_name, reward_tier, submission_timestamp
// --- AGENT 2: Referral Communications Agent (acknowledgement path) ---
// Step 5: Send acknowledgement email
GMAIL POST /gmail/v1/users/me/messages/send
to : referrer_email
subject : 'We have received your referral, ' + referrer_firstname
body : TEMPLATE_ACKNOWLEDGEMENT {
referrer_firstname : referrer_name (first token)
referred_contact_name : referred_contact_name
submission_timestamp : submission_timestamp
next_steps_copy : configured during build
}
// Step 6: Post Slack lead alert
SLACK POST /chat.postMessage
channel : '#sales-leads'
blocks : Block Kit payload {
referrer_name : referrer_name
referred_contact_name : referred_contact_name
referred_contact_email : referred_contact_email
referred_company : referred_company
deal_url : 'https://app.hubspot.com/contacts/{PORTAL_ID}/deal/' + hubspot_deal_id
reward_tier : reward_tier
}
// --- DEAL STAGE CHANGE TRIGGER (continuous, event-driven) ---
// HubSpot fires webhook on every dealstage property change
// for deals where referral_source_name is non-empty
// --- AGENT 3: Reward Processing Agent (stage sync path) ---
HUBSPOT_WEBHOOK POST /your-platform-endpoint/deal-stage-change
payload.objectId -> hubspot_deal_id : string
payload.propertyName : 'dealstage'
payload.propertyValue -> new_stage : string
// Fetch full deal to confirm referral_source_name
HUBSPOT GET /crm/v3/objects/deals/{hubspot_deal_id}
properties: referral_source_name, referral_reward_tier, referral_submitted_at
-> referral_source_name == '' ? HALT (non-referral deal) : CONTINUE
// Step 7/8: Update Google Sheets row
SHEETS GET /v4/spreadsheets/{SHEET_ID}/values/Sheet1!H:H
-> find row index where column H (hubspot_deal_id) == hubspot_deal_id
-> row_index : integer
SHEETS PUT /v4/spreadsheets/{SHEET_ID}/values/Sheet1!G{row_index}:I{row_index}
values[0][0] : stage_label_map[new_stage] // human-readable stage name
values[0][1] : ISO8601 now() // last_updated
// --- CLOSED WON BRANCH ---
// new_stage == 'closedwon' ? EXECUTE_REWARD_PATH : END
// Step 9: Resolve reward amount from tier mapping
TIER_MAP = { 'Tier 1': 500, 'Tier 2': 250, 'Tier 3': 100 } // configure at setup
reward_amount = TIER_MAP[referral_reward_tier]
// If referral_reward_tier not in TIER_MAP -> log error, alert, halt
// Step 10: Create Xero draft bill
XERO POST /api.xro/2.0/Invoices
Type : 'ACCPAY'
Status : 'DRAFT'
Contact.ContactID : xero_contact_id // pre-confirmed referrer record
DueDate : submission_timestamp + 14 days
LineItems[0].Description : 'Referral reward: ' + referred_contact_name + ' - ' + referral_reward_tier
LineItems[0].UnitAmount : reward_amount
LineItems[0].AccountCode : REFERRAL_EXPENSE_ACCOUNT_CODE // confirm with finance
LineItems[0].Quantity : 1
-> response.Invoices[0].InvoiceID -> xero_bill_id : string
// Update Google Sheets row: status + reward amount + xero_bill_id
SHEETS PUT /v4/spreadsheets/{SHEET_ID}/values/Sheet1!G{row_index}:J{row_index}
values[0][0] : 'Pending Reward'
values[0][1] : ISO8601 now()
values[0][2] : reward_amount
values[0][3] : xero_bill_id
// --- AGENT 3 -> AGENT 2 HANDOFF ---
// Closed Won event also triggers Agent 2 reward notification path
// Passed fields: referrer_email, referrer_name, reward_amount,
// referred_contact_name, xero_bill_id
// --- AGENT 2: Referral Communications Agent (reward notification path) ---
// Step 11: Send reward notification email
GMAIL POST /gmail/v1/users/me/messages/send
to : referrer_email
subject : 'Your referral reward has been approved'
body : TEMPLATE_REWARD_NOTIFICATION {
referrer_firstname : referrer_name (first token)
reward_amount : reward_amount (formatted as $NNN)
expected_payment_date: xero_bill_due_date (formatted as DD Month YYYY)
referred_contact_name: referred_contact_name
}
// --- END OF AUTOMATED FLOW ---
// Finance contact opens Xero, reviews DRAFT bill, approves and schedules payment.
// Step 12 (register marked Rewarded) is a manual update by the finance contact
// or can be automated via a future Xero payment webhook if required.04Recommended build stack
More documents for this process
Every document generated for Referral Programme Management.