Package Information
Documentation
n8n-nodes-smartproposal
An n8n community node package for SmartProposal — create and manage branded, trackable video proposal pages, attach documents, read stats, and start workflows when a client views, accepts, or asks a question about a proposal.
This package contains:
- SmartProposal (action node) — drive the SmartProposal API: Proposals, Documents, Events, Agents, Reports, Webhook Endpoints, and the Account behind your key.
- SmartProposal Trigger (webhook node) — start a workflow on
proposal.created,proposal.viewed,proposal.accepted,proposal.question_asked,proposal.document_opened,proposal.updated, orproposal.archived, with automatic endpoint registration and HMAC signature verification.
Table of Contents
- Installation
- Credentials
- SmartProposal (Action Node)
- SmartProposal Trigger
- Recipe: create, enrich, and react
- Compatibility
- License
Installation
Community Node (Recommended)
- In your n8n instance, go to Settings > Community Nodes
- Select Install
- Enter
@belmontdigitalmarketing/n8n-nodes-smartproposal - Agree to the risks and click Install
Manual Installation
For self-hosted n8n instances:
cd ~/.n8n/custom
npm install @belmontdigitalmarketing/n8n-nodes-smartproposal
Then restart n8n.
Credentials
One credential type, SmartProposal API, is used by both nodes.
| Field | Required | Description |
|---|---|---|
| Base URL | Yes | Your agency's API base including /api/v1, e.g. https://your-agency.smartproposal.pro/api/v1. The API is also served on your portal host and on a custom domain. |
| API Key | Yes | An sp_live_… (or sp_test_… on staging) key from Settings → Integrations → SmartProposal automation → API keys. Sent as Authorization: Bearer <key> on every request. One key per tool; revoke individually. |
Click Test to verify — it calls GET /me, so a 200 confirms the key, the base URL, and that SmartProposal is enabled for the agency.
The trigger needs no second credential. SmartProposal issues each webhook endpoint its own signing secret when the node registers it, and the node keeps that secret to verify deliveries.
SmartProposal (Action Node)
Choose a Resource, then an Operation.
Proposal
| Operation | What it does |
|---|---|
| Create | POST /proposals. Client First/Last Name and Video URL are required; Email, Phone, CRM ID, Opportunity ID, AMS ID, Custom Message, Agent, and link-type Documents are optional. Returns the proposal including public_url (live immediately) and documents[]. Fires proposal.created. |
| Get | GET /proposals/{id}. Full detail: documents[], events[] (newest first, max 200), plus accepted and last_question — the latest Accept / Question submissions with their form data. |
| Get Many | GET /proposals with Filters (below). Archived proposals are excluded unless you set Include Archived or name archived in the Status filter. |
| Update | PATCH /proposals/{id}. Update Fields for client name/email/phone, CRM ID, Opportunity ID, AMS ID, Custom Message, Video URL (re-resolved), plus the top-level Agent picker. Adding a nullable field and leaving it empty clears it (Custom Message empty = back to the agency greeting). Fires proposal.updated with data.changed[]. |
| Archive | Soft delete — the client link stops working, the record stays. Fires proposal.archived. |
| Restore | Un-archives; status is recomputed from what actually happened (accepted beats question beats viewed beats sent). Fires proposal.updated with changed: ["restored"]. |
Video URL accepts Loom, YouTube, Vimeo, Wistia, and Vidyard share links, any page that exposes oEmbed, or a raw <iframe> embed. SmartProposal resolves it on save; an unrecognised URL fails with a clear 422 naming the field.
CRM ID, Opportunity ID, and AMS ID are free-text external identifiers (CRM contact, CRM opportunity, agency-management-system record). All three are echoed on every proposal object and webhook payload, adjacent to each other, and are exact-match filters on Get Many.
Custom Message is a per-proposal greeting that replaces the agency's greeting template on that proposal's page only. It is plain text sent exactly as written: line breaks, blank lines, and spacing are preserved (CRLF is normalised to LF) and HTML is not interpreted. Merge fields are rendered when the client views the page: {{first_name}}, {{last_name}}, {{agent_first_name}}, {{agent_name}}, {{agency_name}}. Max 5,000 characters. Leave it empty to use the agency greeting.
Documents on Create are link-type only (a Google Drive folder becomes an embedded folder grid, a Drive file an embedded preview, anything else a button). To store a file on the platform — imported from a URL or uploaded from binary data — create the proposal first, then use Document → Add.
Document
| Operation | What it does |
|---|---|
| Add | POST /proposals/{id}/documents. Choose a Source: Link or Google Drive URL (attach a link), Import File From URL (SmartProposal downloads the file and stores it — PDF/PNG/JPG, ≤ 25 MB, give it a direct download URL such as a Drive uc?export=download link), or Upload Binary File (multipart upload from an n8n binary field, default data). Label is optional. Fires proposal.updated. |
| Delete | DELETE /proposals/{id}/documents/{docId}. The Document picker lists the documents on the selected proposal. |
Imported and uploaded documents render inline on the proposal page with open/download tracking; each open fires proposal.document_opened.
Event
| Operation | What it does |
|---|---|
| Get Many | GET /proposals/{id}/events — the proposal's event log, newest first (the API returns up to 200). Optional Event Types filter narrows to e.g. accepted / question_asked. Each event carries type, data (the submitted form for accept/question), actor_user_id, and created_at. |
Agent
| Operation | What it does |
|---|---|
| Get Many | GET /users — the agency's users: id, name, first_name, last_name, email, role, crm_id, phone, title, is_active. Filters: Active Only, Role. Handy for mapping a CRM owner to a SmartProposal agent. |
Report
| Operation | What it does |
|---|---|
| Get | GET /stats → total, viewed, accepted, question_asked, view_rate, accept_rate, avg_hours_to_first_view, and by_agent[]. Filters: From, To (by creation date), Agent. Archived proposals are excluded. |
Webhook Endpoint
Manage the same endpoints the Settings → Integrations → Webhook endpoints card shows. You rarely need these when using the Trigger (it registers itself), but they let a workflow point other tools at SmartProposal.
| Operation | What it does |
|---|---|
| Create | POST /webhooks with URL, Events, optional Description / Active. The response includes the endpoint's secret (whsec_…). |
| Get / Get Many | Endpoint details including the secret, last delivery status, and consecutive-failure count. |
| Update | Change URL, Events (replaces the subscription), Description, Active, or Rotate Secret (new secret in the response). |
| Delete | Remove the endpoint. |
| Get Deliveries | GET /webhooks/{id}/deliveries — recent attempts, newest first, with status, attempt, response_code, response_snippet, next_retry_at. |
Account
| Operation | What it does |
|---|---|
| Get | GET /me → the tenant (id, slug, name), the API key's name / prefix / scopes, proposal_host, and portal_url. |
Referencing a Proposal
Every operation that acts on one proposal uses a Proposal picker with three modes:
- From List — searchable dropdown (
Client Name · status · created · CRM id), backed by the API's free-text search. - By ID or Slug — paste the uuid or the public slug (the
5AxndQ7AsqTHpart of the public URL). The API accepts either. - By Public URL — paste
https://…/p/{slug}; the node extracts the slug.
Expressions work in any mode, so {{ $json.proposal.id }} from a Trigger payload drops straight in.
Referencing an Agent
The Agent picker on Create/Update accepts a user id, an email, or a display name (case-insensitive). Names and emails are resolved against GET /users before the request; an unmatched value throws listing the valid agents rather than letting the API guess. Leave it empty on Create to assign the agency owner, or on Update to leave the agent unchanged.
Inside Filters (Get Many, Report), Agent is a plain dropdown; an expression carrying an email or name is resolved the same way.
Get Many: Return All & Filters
Proposal → Get Many offers Return All; when off, set a Limit. The node pages through the API (page size 100) either way. Filters:
| Filter | API param |
|---|---|
| Agent | agent_id |
| Created After / Before | created_after / created_before (ISO; date-only values cover the whole day) |
| CRM ID / Opportunity ID / AMS ID | exact match |
| Include Archived | include_archived=1 |
| Search | q — client name, email, phone, CRM / opportunity / AMS id, slug |
| Sort | created (default), updated, activity (last view) |
| Status | csv of sent, viewed, question_asked, accepted, archived (the portal labels sent as New) |
Each returned proposal is one output item. The list envelope's total and per-status counts are not emitted; use Report → Get for aggregate numbers.
Include Link to Workflow
Proposal Create and Update offer an Include Link to Workflow toggle (default off). When enabled, the node adds a workflowLink property to its output — useful for downstream Slack/email steps. It deliberately does not write into the proposal, which is a public, client-facing page.
Errors and Rate Limits
API errors arrive as { error: { code, message, fields } }. The node surfaces the message with each field problem named after its UI label, e.g. Validation failed — Video URL: couldn't detect a video at that URL. Every key is limited to 120 requests / minute; a 429 is reported with the Retry-After value so you can add a Wait node.
SmartProposal Trigger
Listens for SmartProposal's outbound events and emits each delivery body as one item.
Endpoint Registration (automatic)
When you activate the workflow, the trigger creates a webhook endpoint in SmartProposal (POST /webhooks, using your API key) pointing at its production webhook URL, subscribed to exactly the Events you selected, described as n8n: <workflow name>. When you deactivate, it deletes that endpoint. Nothing to copy into the SmartProposal UI.
The endpoint's signing secret is returned by the API at creation and stored in the node's static data. If the node ever finds a matching endpoint it did not create (same URL), it reads the secret from GET /webhooks/{id} instead.
Signature Verification
With Verify Signature on (default), the node computes HMAC-SHA256(secret, rawBody) and compares it (timing-safe) against the X-SmartProposal-Signature: sha256=<hex> header. A mismatch — or a missing secret/signature — is rejected with HTTP 401 and nothing is emitted. SmartProposal also sends Authorization: Bearer <secret>; the node uses the signature, which covers the body.
Verification hashes the raw request bytes. If you proxy webhooks through middleware that rewrites the body, the signature may not match — turn verification off only in that case.
Emit Test Pings (default off) controls whether the Test button in SmartProposal's Integrations settings (event: "proposal.test") starts the workflow. Test pings carry no proposal object, so leave it off for production workflows.
Event Payloads
Every delivery is:
{
"event": "proposal.accepted",
"timestamp": "2026-09-22T20:41:12.331Z",
"delivery_id": "uuid",
"attempt": 1,
"tenant": { "id": "…", "slug": "your-agency", "name": "Your Agency" },
"proposal": { "id", "slug", "public_url", "status", "crm_id", "opportunity_id", "ams_id",
"client": {…}, "agent": { "id", "name", "email", "crm_id" }, "custom_message",
"video_url", "documents": [ { "id", "label", "kind", "url", "open_count" } ],
"view_count", "first_viewed_at", "last_viewed_at", "accepted_at", "last_question_at", "created_at" },
"data": { … }
}
| Event | data |
|---|---|
proposal.created |
{ via: "portal" | "api", api_key? } |
proposal.viewed |
{ view_number, first_view } — every human view |
proposal.document_opened |
{ document_id, label, kind } |
proposal.accepted |
the Accept form: start_date, home_billing, mortgage_company, loan_number, auto_billing, comments |
proposal.question_asked |
{ contact_preference, phone | email, topics[], question } |
proposal.updated |
{ changed[] } (e.g. ["documents"], ["video_url"], ["restored"]) |
proposal.archived |
{} |
proposal.status is the most significant thing that happened (accepted beats question beats viewed); the timestamps tell the full story, e.g. accepted and later asked a question.
Retries and Idempotency
SmartProposal expects a 2xx within 10 s (the node responds immediately, before your workflow runs). Anything else is retried after 1 min, 10 min, then 1 h, then marked failed. A retried delivery has the same delivery_id and a higher attempt, so de-duplicate on delivery_id if your workflow is not naturally idempotent.
Recipe: create, enrich, and react
- An agent creates the proposal from the CRM (the
/newpage) or your workflow calls Proposal → Create withcrm_id/opportunity_id. proposal.createdarrives on the Trigger withpublic_url. The workflow finds the client's quotes folder in Drive and calls Document → Add (Link) with the folder URL, or Import File From URL with each PDF.- Shorten
public_url, text/email it to the client, update the CRM opportunity. proposal.accepted/proposal.question_askedarrive;datacarries the form. Move the opportunity stage and notify the agent.
Or gather everything first and make a single Proposal → Create with Documents included — both work.
Compatibility
- n8n: 1.x
- Node.js: 18.10 or later