Quick Start
Get a working automation in four steps
Start with one campaign and one event. Add more workflow logic after you confirm the connection works.
-
1
Create an API keyOpen Integrations > API Keys. Give the key a clear label and copy it when it appears. The full key is shown once.
-
2
Test the connectionSend an authenticated request to GET /api/v1/account. A valid connection returns safe account details and API version v1.
-
3
Create a campaignSend the client and quote details to POST /api/v1/campaigns. Include external_reference so workflow retries do not create duplicates.
-
4
Receive QuoteNag eventsAdd your automation tool's HTTPS URL under Integrations > Webhooks. Filter incoming events by the exact event names documented below.
Authentication
Send your API key as a bearer token
Every API request must include the key in the Authorization header. QuoteNag does not accept API keys in URLs, query strings, or JSON bodies.
Authorization: Bearer qn_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Find your key in QuoteNagSign in, open Integrations, then choose API Keys. If you lose the complete key, revoke it and create a new one.
Test Your Connection
Use this endpoint before creating campaigns. It confirms the key, account, subscription access, and API version without returning campaign or billing data.
GET https://quotenag.gottools.co/api/v1/accountAuthorization: Bearer qn_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json
HTTP 200 response
{
"data": {
"id": 123,
"name": "Lyte Painting",
"api_version": "v1"
}
}
API Reference
Create, find, and stop quote campaigns
All API requests are restricted to the QuoteNag account that owns the bearer key. An API request cannot choose another account.
POST
/api/v1/campaigns
Creates a quote campaign and schedules its follow-ups. Each QuoteNag account can create up to 500 campaigns per calendar month.
| Field | Required | What it does |
client_name | Yes | Client name, up to 120 characters. |
client_email | Yes | Valid client email, up to 190 characters. |
quote_title | Yes | Short quote title, up to 190 characters. |
quote_summary | Yes | Useful context about the work or estimate. |
quote_amount | No | Amount as a string or number using QuoteNag's existing display convention. |
quote_expires_at | No | A non-past YYYY-MM-DD date in the account timezone. |
external_reference | No | Your source record ID. Recommended for retry protection. Maximum 160 characters. |
followups | No | One to three custom follow-ups. Omit it to use the safe schedule below. |
Minimum request
{
"client_name": "Jamie Rivera",
"client_email": "jamie@example.com",
"quote_title": "Roof repair quote",
"quote_summary": "Repair damaged shingles and flashing.",
"external_reference": "estimate-1042"
}
What happens when you omit the schedule
QuoteNag creates one runnable follow-up scheduled three days after campaign creation. It uses the account's first saved follow-up subject and body, or QuoteNag's built-in first-message text if that template is empty. QuoteNag does not currently have an account-level default cadence.
Custom follow-up request example
{
"client_name": "Morgan Lee",
"client_email": "morgan@example.com",
"quote_title": "Kitchen remodel quote",
"quote_summary": "Cabinets, counters, and installation.",
"quote_amount": "$12,500.00",
"quote_expires_at": "2030-08-15",
"external_reference": "estimate-1042",
"followups": [
{
"timing_type": "days",
"days": 2,
"subject": "Quick check-in on {{quote_title}}",
"body": "Hi {{client_name}}, just checking in on the quote."
}
]
}
Duplicate protection
external_reference is unique inside one QuoteNag account and comparison is case-sensitive. An identical retry returns the existing campaign with HTTP 200 and deduplicated: true. Reusing the same reference with different campaign data returns HTTP 409 and leaves the original campaign unchanged. Requests without an external reference always create a new campaign.
New campaign response
{
"data": {
"id": 123,
"campaign_url": "https://quotenag.gottools.co/app/campaign/123",
"external_reference": "estimate-1042",
"client_name": "Jamie Rivera",
"client_email": "jamie@example.com",
"quote_title": "Roof repair quote",
"quote_summary": "Repair damaged shingles and flashing.",
"quote_amount": null,
"quote_expires_at": null,
"status": "Pending",
"created_at": "2026-07-10 12:00:00",
"updated_at": "2026-07-10 12:00:00",
"deduplicated": false
}
}
GET
Find Quote Campaign
Use one of these focused lookups. QuoteNag does not perform partial title searches or general CRM searches.
GET /api/v1/campaigns/123Returns one owned campaign by ID, or HTTP 404.
GET /api/v1/campaigns?external_reference=estimate-1042Exact, account-scoped reference. Returns a list with zero or one match.
GET /api/v1/campaigns?client_email=morgan%40example.comEmail is not unique. Returns up to 50 matches, newest first.
GET /api/v1/campaignsReturns the account's latest 50 campaigns.
Collection lookups return {"data":[]} when nothing matches. Do not send both supported collection filters in one request.
POST
/api/v1/campaigns/{id}/stop
Stops a Pending campaign without deleting it. QuoteNag cancels remaining scheduled follow-ups and preserves sent or failed history. No request body is required.
- Pending: changes to
Stopped and cancels remaining scheduled follow-ups.
- Stopped: returns a safe HTTP 200 result without changing the status.
- Accepted or Declined: remains resolved and is never overwritten with
Stopped.
- Another account or unknown ID: returns the same non-leaking HTTP 404 response.
POST https://quotenag.gottools.co/api/v1/campaigns/123/stop
Authorization: Bearer qn_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Webhooks
Send quote activity to your other tools
QuoteNag sends a signed JSON request to every active webhook endpoint after supported campaign activity. Add endpoints under Integrations > Webhooks.
campaign.createdSent after a manual or API campaign and its follow-ups commit successfully.
campaign.acceptedSent once for the first valid accepted decision. Filter on data.decision = accepted.
campaign.declinedSent once for the first valid declined decision. Filter on data.decision = declined.
followup.sentSent after the mailer succeeds and QuoteNag records the follow-up as sent.
Existing events followup.failed and integration.test also remain available. They are not part of the four Pabbly trigger mappings above.
Stable event envelope
{
"id": "evt_11111111111111111111111111111111",
"event": "campaign.created",
"sent_at": "2026-07-10T19:00:00+00:00",
"occurred_at": "2026-07-10T19:00:00+00:00",
"api_version": "v1",
"data": {}
}
The event ID stays the same across retry attempts for one logical event. Receivers should ignore unknown additive fields so future compatible additions do not break the workflow.
Webhook signing
QuoteNag signs the exact raw request body with the endpoint's signing secret. The signature is a lowercase hexadecimal HMAC-SHA256 digest with the sha256= prefix.
X-QuoteNag-Event: campaign.created
X-QuoteNag-Event-ID: evt_11111111111111111111111111111111
X-QuoteNag-API-Version: v1
X-QuoteNag-Signature: sha256=<hex HMAC digest>
Verify the signature against the raw body before parsing or acting on the payload.
Delivery and retries
- Any HTTP 2xx response counts as successful delivery.
- Transport errors, HTTP 408, 425, 429, and 5xx responses receive up to three synchronous attempts.
- Other HTTP 4xx responses are not retried.
- Each attempt has a 10-second timeout and redirects are not followed.
- Delivery requires PHP cURL and is pinned to a validated public destination IP to reduce server-side request forgery risk.
- QuoteNag does not currently provide a delayed replay queue or dead-letter screen.
Webhook Event Payloads
These sanitized examples are loaded from the same checked fixtures used to verify QuoteNag's production serializers.
New Quote Campaign: campaign.created
{"error":"Example unavailable"}
Quote Accepted: campaign.accepted
{"error":"Example unavailable"}
Quote Declined: campaign.declined
{"error":"Example unavailable"}
Follow-Up Sent: followup.sent
{"error":"Example unavailable"}
Manual Pabbly Setup
Connect Pabbly before the native QuoteNag app is public
This setup uses Pabbly's webhook and HTTP/API request steps. It does not use or imply a live native QuoteNag app.
No per-event selector in QuoteNagEvery active QuoteNag endpoint receives every supported event. Use a Pabbly filter on the exact event value, plus data.decision for accepted or declined workflows.
Receive QuoteNag events in Pabbly
Create a workflow in Pabbly Connect.
Choose the appropriate Webhook by Pabbly trigger and copy its generated HTTPS webhook URL.
Use Send test to confirm connectivity. This sends integration.test.
To capture the fields for a real trigger, perform the matching action in a test campaign: create a campaign, accept or decline it, or let a scheduled follow-up send.
In Pabbly, capture the request and filter it using the mapping below.
| Workflow outcome | Filter |
| New Quote Campaign | event = campaign.created |
| Quote Accepted | event = campaign.accepted and data.decision = accepted |
| Quote Declined | event = campaign.declined and data.decision = declined |
| Follow-Up Sent | event = followup.sent |
Create QuoteNag campaigns from Pabbly
Create a QuoteNag API key and store it in Pabbly's protected authentication or header field.
Add an HTTP/API request action in Pabbly.
Set the method to POST and the URL to https://quotenag.gottools.co/api/v1/campaigns.
Add Authorization: Bearer YOUR_QUOTENAG_API_KEY and Content-Type: application/json.
Map the four required fields and a stable external_reference from the source quote or estimate.
Test the request, then confirm the campaign appears in QuoteNag.
{
"client_name": "Jamie Rivera",
"client_email": "jamie@example.com",
"quote_title": "Roof repair quote",
"quote_summary": "Repair damaged shingles and flashing.",
"external_reference": "estimate-1042"
}
Error Responses
Use the status and code to choose the next step
| HTTP | Code | What to do |
| 401 | missing_api_key | Add the bearer Authorization header. |
| 401 | invalid_api_key | Check the key and bearer format. Do not put the key in the URL. |
| 401 | revoked_api_key | Create a replacement key and update the workflow. |
| 401 | inactive_account | Restore account access before retrying. |
| 402 | subscription_required | Activate or restore the QuoteNag subscription. |
| 404 | resource_not_found | Check the campaign ID. QuoteNag returns the same result for unknown and unowned records. |
| 409 | external_reference_conflict | Do not overwrite the existing campaign. Use the source record's correct stable reference. |
| 429 | campaign_monthly_limit_reached | The account has created 500 campaigns this calendar month. Wait until next month before creating another new campaign. |
| 422 | validation_error | Fix the named field and stable reason before retrying. |
| 422 | invalid_request | Check JSON syntax and request-level values. |
{
"error": "Client email is invalid.",
"code": "validation_error",
"field": "client_email",
"reason": "invalid_email"
}
Security
Keep the connection useful without exposing the keys
Keep API keys privateStore them in protected credential fields. Never add them to URLs, screenshots, source control, normal logs, or support messages.
Revoke compromised keysRevoke the affected key under Integrations > API Keys. Revocation takes effect on the next request.
Verify webhook signaturesGenerate HMAC-SHA256 from the exact raw request body and compare it with X-QuoteNag-Signature.
Use HTTPSUse HTTPS for API calls and all new webhook endpoints. QuoteNag keeps HTTP webhook support only for backward compatibility with existing consumers.
Expect account isolationAPI keys, campaign lookups, duplicate checks, and webhook endpoints stay inside the owning QuoteNag account.