smartproposal

n8n community node for SmartProposal — create and manage video proposal pages, add documents, read stats, and trigger workflows on signed proposal events

Package Information

Released: this month
Downloads: 0 weekly / 0 monthly
Latest Version: 0.2.0
Author: Belmont Digital Marketing

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, or proposal.archived, with automatic endpoint registration and HMAC signature verification.

Table of Contents

Installation

Community Node (Recommended)

  1. In your n8n instance, go to Settings > Community Nodes
  2. Select Install
  3. Enter @belmontdigitalmarketing/n8n-nodes-smartproposal
  4. 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 5AxndQ7AsqTH part 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

  1. An agent creates the proposal from the CRM (the /new page) or your workflow calls Proposal → Create with crm_id / opportunity_id.
  2. proposal.created arrives on the Trigger with public_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.
  3. Shorten public_url, text/email it to the client, update the CRM opportunity.
  4. proposal.accepted / proposal.question_asked arrive; data carries 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

License

MIT

Discussion