Your site, CRM, Zap, or chat agent can create a client and a draft. A human still previews and sends. Nothing auto-sends.

This is Team+. You need at least one company in the workspace.
When to use this
| Situation | Use Incoming Webhooks |
|---|---|
| A website or form should save a lead | Yes — POST from your server, never from the browser |
| Zapier / Make / n8n should create a client or draft | Yes |
| A chat agent (Telegram bot, etc.) should open a draft | Yes — the agent POSTs; chat apps themselves only send alerts |
| People already live in a CRM | Prefer CRM sync. Use a webhook only for new form leads |
Activate an endpoint
- Open Integrations → Incoming Webhooks.
- Create an endpoint. Give it a name you will recognize later (for example “Website form”).
- Copy and store these two values in your server env (or Zap / agent secrets). Never put them in frontend JavaScript, a public repo, or a form builder’s client-side webhook field.
| Item | What it is |
|---|---|
| URL | POST {app}/api/webhooks/{token} |
| Signing secret | Used as Authorization: Bearer <secret> or X-Webhook-Signature: sha256=<hex> |
- Keep the endpoint Active. Disable it if you rotate the secret or retire the form.
- Send a test POST (curl below, or the sample in the card). A client named “Test Lead” should appear under Clients.
Optional: copy a template ID from Templates (key icon) if proposal.draft should open a priced pack instead of a blank draft. Visitors cannot pass a template ID in a public form field — keep it on the server.
If a CRM card has Push webhook clients to CRM on, these upserts also add/update the contact there. Off by default. Never deletes.
Auth
Bearer (simplest for a trusted server, Zap, or agent):
POST /api/webhooks/{token}
Authorization: Bearer <signingSecret>
Content-Type: application/json
HMAC (when the secret should not travel as a bearer token):
POST /api/webhooks/{token}
X-Webhook-Signature: sha256=<hex>
Content-Type: application/json
hex is lowercase HMAC-SHA256(signingSecret, rawRequestBody). The header may include a sha256= prefix.
Events
type | Result |
|---|---|
client.upsert | Create or update a Client. Match on external_id when you send one |
client.create | Same shape as upsert — create path |
client.update | Same shape — requires client.external_id |
proposal.draft | Upsert optional client + open a draft (blank, or from template_id / template_key) |
Fields you can send
Top-level JSON only. InProQu does not accept a raw HTML form post — your server, Zap, or agent maps fields into this shape.
Always
| Field | Required | Notes |
|---|---|---|
type | Yes | One of the events above |
client object
Required for client.*. Optional on proposal.draft (omit only if you will attach the client later by hand).
| Field | Required | Notes |
|---|---|---|
name | Yes (when client is sent) | Person’s name |
email | No | Valid email when present. Needed later for in-app send |
company | No | Organization, not the person |
phone | No | |
notes | No | Extra context (message, source, budget) |
external_id | No | Your stable ID (CRM id, form lead id). Same value updates the same client. Required for client.update |
These land on the Client record. On the document they fill {{client_name}}, {{client_email}}, {{client_company}}, and {{client_phone}}.
proposal object
Required for proposal.draft.
| Field | Required | Notes |
|---|---|---|
title | Yes | Document title |
project_name | No | Shown as the project name on the draft |
template_id | No | Workspace or catalog template cuid from Templates (key icon) |
template_key | No | Optional catalog layout key. Omit both for a blank draft |
Do not invent other keys. Pricing, billing, and body copy stay in the template or the builder — the webhook does not set line items.
Sample payloads
Client only
{
"type": "client.upsert",
"client": {
"name": "Alex Rivera",
"company": "Northwind Labs",
"email": "[email protected]",
"phone": "+1 555 0100",
"notes": "Needs a brochure site by Q3",
"external_id": "crm-123"
}
}
Client + draft from a template
{
"type": "proposal.draft",
"client": {
"name": "Alex Rivera",
"company": "Northwind Labs",
"email": "[email protected]",
"external_id": "crm-123"
},
"proposal": {
"title": "Website rebuild quote",
"project_name": "Marketing site",
"template_id": "clxxxxxxxx"
}
}
Success response
Client only: ok, clientId, urls.client.
Draft: ok, proposalId, clientId, optional templateId, and:
| URL | Who sees it |
|---|---|
urls.builder | Your team — open and finish the draft |
urls.preview | Your team — preview before share |
urls.client | Your team — the Client record |
Paste urls.builder into Slack or the agent chat. Do not put builder URLs on a public thank-you page.
Quick curl test
curl -sS -X POST "$INPROQU_WEBHOOK_URL" \
-H "Authorization: Bearer $INPROQU_WEBHOOK_SECRET" \
-H "Content-Type: application/json" \
-d '{"type":"client.upsert","client":{"name":"Test Lead","email":"[email protected]"}}'
Sample prompts
Copy into Cursor, Claude, or your agent. Replace the CAPS placeholders.
Wire an existing form (client only)
Connect my website contact form to InProQu Incoming Webhooks.
Requirements:
- On submit, call MY server endpoint (never call InProQu from the browser).
- Server POSTs JSON to INPROQU_WEBHOOK_URL with Authorization: Bearer INPROQU_WEBHOOK_SECRET.
- Body type "client.upsert" with client.name (required), plus client.email, client.company, client.phone, client.notes from the form when present.
- Keep the signing secret in env vars only.
- Return a generic success to the visitor; log InProQu errors server-side.
- Do not expose builder URLs or webhook secrets to the frontend.
My stack: DESCRIBE_STACK (e.g. Next.js App Router / Cloudflare Worker / PHP).
Form fields: LIST_FIELD_NAMES.
Webhook URL and secret are already in env as INPROQU_WEBHOOK_URL and INPROQU_WEBHOOK_SECRET.
Form → client + draft from a template
When someone submits my “Request a proposal” form, create an InProQu client and open a draft document from a template.
- Server-side only POST to Incoming Webhooks.
- type: "proposal.draft"
- Map form fields to client.name, client.email, client.company, client.phone, client.notes.
- proposal.title from company or a default like "Website proposal for {company}".
- proposal.template_id = TEMPLATE_ID_FROM_INPROQU_TEMPLATES.
- Auth: Bearer INPROQU_WEBHOOK_SECRET.
- On success, notify MY_TEAM_CHANNEL with urls.builder from the JSON response (internal only).
- Visitor only sees a thank-you message.
Stack: DESCRIBE_STACK.
Chat agent tool
Add a tool that creates an InProQu client and draft via Incoming Webhooks.
- POST JSON to INPROQU_WEBHOOK_URL
- Header Authorization: Bearer INPROQU_WEBHOOK_SECRET
- type "proposal.draft" with client.name (required), client.email, client.company, client.phone, client.notes, client.external_id
- proposal.title, proposal.project_name, and proposal.template_id from env TEMPLATE_ID
- On success, reply in chat with urls.builder only (for the human on my team)
- Never print the signing secret or put builder links in a customer-facing message
After a draft appears
- Open
urls.builder. - Check company, client, pricing, How to pay.
- Preview Website and Document.
- Share — that is the first time the client sees it.
Outbound (document events → other tools)
Team+ can also POST view / share / accept (and related events) to a Zapier catch hook, Make, n8n, or any HTTPS URL you paste.
Payloads include source: "inproqu". Use this for ops (Slack is often simpler via the messaging card). Outbound does not create clients — that is incoming.
If something fails
| Status / symptom | Meaning |
|---|---|
| 401 | Bad Bearer secret or HMAC |
| 402 | Plan does not allow inbound webhooks |
422 company_required | Add a company profile first |
| 422 invalid payload | Fix JSON shape / required fields (client.name, or proposal.title on drafts) |
| 429 | Rate limited — retry later |
| Template errors | Missing or locked template ID — use a valid Templates ID |
| Two drafts from one submit | Your form or Zap fired twice. Send one POST per lead |
Related: How the workspace fits together · Start a document · Add clients.