API & Webhooks — connect Wraptor to Zapier and your own tools
Who it's for
Owners (and the tech-comfortable manager) who want Wraptor talking to the rest of their stack: Zapier automations, a custom dashboard, a spreadsheet that pulls live numbers, or lead sources that push straight into the Lead Inbox. Included with Wraptor Shop and Wraptor Fleet.
Where it fits
Outside the job workflow itself — this is the integration layer around it. Events fire from the moments that matter to the shop: a lead arrives, a job moves a stage, a customer approves a quote, a payment lands.
Overview
Two halves:
- The REST API (
https://wraptor.app/api/v1) lets outside software read your customers, jobs, quotes, invoices, leads and purchase orders — and create customers and leads. Every request authenticates with an API key you mint in Settings. - Outbound webhooks push events to a URL you control the moment they happen, signed so your receiver can prove they came from Wraptor. This is what a Zapier trigger rides on.
The machine-readable spec lives at /api/v1/openapi.json.
Screens & navigation
Settings → API & Webhooks (under Integrations & Billing, or straight
to /settings?section=developer). One panel, two cards:
- API Keys — every active key on one row: its name, its display prefix
(
wrk_live_a1b…), either "used 3d ago" or "never used", and a Revoke… link at the end. - Webhook Endpoints — every registered URL with the events under it ("All events" when you picked none), a green dot when it is on and a grey one when it is off, and Test · Disable/Enable · Delete…. Underneath, Recent Deliveries lists the last 20 real deliveries: the event name, the attempt number once one has been retried, the error and HTTP code on a failure, and how long ago it ran.
Capabilities
API keys
- Create key — name it after what will hold it ("Zapier", "reporting sheet"). The full key appears once, in a copy box headed "Your new API key — copy it now, it won't be shown again." Only a hash and the first twelve characters are stored; Wraptor cannot show it again.
- Revoke… — a two-step inline confirm. It takes effect immediately.
- Up to 10 active keys per shop. Each key is shop-wide: full access to everything below, with no per-resource scopes.
- The last-used time is refreshed at most every five minutes, so a busy key can read a few minutes behind.
REST API
- List and get by id:
customers,jobs,quotes,invoices,purchase-orders. Leads are list-only. - Write:
POST /customersandPOST /leads, both answering201. - Lists paginate by cursor:
?limit=(25 by default, 100 at most) and?cursor=taken from the previous page'snext_cursor, which isnullon the last page.?updated_since=(an ISO 8601 timestamp) narrows a list to what changed since your last poll — the Zapier polling filter. Leads never change once captured, so theirs filters on the created time. - A successful read returns
{ "data": … }, a list addsnext_cursor, and anything that fails returns{ "error": { "code": …, "message": … } }with a matching HTTP status. GET /meverifies a key and names the shop (Zapier's auth test).- 120 requests a minute per key; over the limit returns
429with aRetry-Afterheader. GET /jobsleaves out the seeded sample job.- An API-created lead lands in the Lead Inbox and rings the bell ("New API
lead: …"). Its source is
api, and it files under the Directory filter on Leads → Leads.
Webhook events
job.created, job.stage_changed, quote.approved, quote.declined,
proof.approved, proof.changes_requested, document.signed, invoice.paid,
invoice.deposit_paid, lead.created, booking.created, customer.created,
inspection.completed, purchase_order.ordered, purchase_order.received,
call.completed, call.missed, payroll.finalized.
Every delivery uses the same envelope — event, entity_type, entity_id,
created_at, and a data object holding that event's own fields:
{
"event": "job.stage_changed",
"entity_type": "job",
"entity_id": "…",
"created_at": "2026-09-06T15:04:05.000Z",
"data": { "job_number": "…", "title": "…", "from": "…", "to": "…" }
}
job.createdfires when a job is created in the app — the New Job form, or Rex on your say-so. A job minted by a quote approval does not repeat it — that arrives asquote.approved, whose payload carries the newjob_id— and neither do jobs added by a bulk import.quote.approvedandquote.declinedfire when the customer answers on their own quote page. Recording the yes yourself with the Mark approved sheet does not fire them.invoice.paidandinvoice.deposit_paidfire for card and bank payments through Stripe and for payments you record by hand (cash, Zelle, check). Either way the payload carriesinvoice_number,amount,method,invoice_total,fully_paidandjob_id.inspection.completedfires when a check-in/check-out vehicle inspection is submitted (either lane — installer tablet or front desk). The payload carries the report id, job id, kind, each checklist item's grade, photo and video counts, and the signer name/time — no file URLs.purchase_order.orderedfires when a PO is marked ordered or emailed to the vendor;purchase_order.receivedfires when it becomes fully received (a short-close counts, flaggedshort_closed: true). Payloads carry the PO number, status, vendor id/name, the ordered/expected/received timestamps, line count and cent totals — line detail comes fromGET /purchase-orders/{id}.call.completed/call.missedfire when a call to the shop's tracking number ends (answered vs. not), so they only ever arrive once call tracking is switched on for your shop. Payloads carry the caller and tracking numbers, the matched customer/lead ids (null if unrecognized), and — for completed calls — the duration in seconds. See calls.md.payroll.finalizedfires when a pay period is finalized on Money → Payroll. The payload carries the period start/end, line count, total hours, and base/commission/total cent amounts — deliberately no per-person lines (wages stay inside the app; export the CSV or PDF for detail).
Endpoints
- Add endpoint — paste the URL and tap the events you want. Pick none and
the endpoint takes all of them ("No events selected — the endpoint receives
all events."). The signing secret (
whsec_…) is shown once, right after you add it. Up to 10 endpoints per shop. - Verify — each POST carries
wraptor-id,wraptor-timestamp, andwraptor-signature: v1,<base64 HMAC-SHA256>overid.timestamp.body, keyed with your secret's base64 part (everything afterwhsec_) — the svix scheme, so any svix client library verifies it by mapping the header names. The user agent isWraptor-Webhooks/1.0. - Retries — a failed delivery is due again after 2 minutes, then 8, then every 15, for up to 6 attempts (about 55 minutes end to end). The retry sweep runs every half hour, so a re-send can land later than its due time. An endpoint whose last 3 deliveries all ran out of attempts is switched off and the row reads "auto-disabled after repeated failures — fix the URL and re-enable".
- Test — fires a
test.pingat one endpoint and answers with a Test delivered (HTTP 200) toast, or Test failed and the reason. Test pings stay out of Recent Deliveries, and the button is greyed out while an endpoint is disabled. - Disable / Enable / Delete… — disabling stops deliveries and keeps the endpoint and its secret listed; re-enabling clears the auto-disable stamp; deleting removes it for good.
- Manage endpoints from the API too:
GETandPOST /api/v1/webhooks, thenGET,PATCHandDELETE /api/v1/webhooks/{id}. Create returns the secret once; delete unsubscribes. That is the REST-hook shape Zapier expects.
Step-by-step tasks
-
Connect a spreadsheet or dashboard
- Settings → API & Webhooks → type a name → Create key.
- Copy the key from the one-time box, then click I've saved it.
- Call
GET https://wraptor.app/api/v1/jobswith headerAuthorization: Bearer wrk_live_…. - Follow
next_cursoruntil it is null; poll withupdated_sinceafter.
-
Get notified the moment a lead arrives
- Add endpoint with your receiver URL, subscribed to
lead.created. - Store the one-time secret; verify
wraptor-signatureon each POST. - Click Test to confirm your receiver answers 2xx.
- Add endpoint with your receiver URL, subscribed to
-
Push leads in from an outside form
POST /api/v1/leadswith{ "name": "...", "contact": "...", "vehicle": "...", "service": "...", "timeline": "...", "message": "..." }— onlynameandcontactare required.- The lead appears under Leads → Leads and rings the bell.
Settings & permissions
- API keys and webhooks come with Wraptor Shop and Wraptor Fleet (and the grandfathered Wraptor Pro (original)). On Wraptor Starter the panel reads "API access is included with Wraptor Shop." with an Upgrade to Wraptor Shop → link instead of the two cards.
- The 7-day card-backed trial does not open it — API access needs a live paid
subscription. A failed payment keeps working through the grace period; once
the subscription actually ends, existing keys stop authenticating (
403, codeplan_required) while your endpoints stay registered. - Opening Settings needs the settings permission, and this panel itself runs on the billing permission — reading the key list, creating a key and managing endpoints all require it. Owners and Managers have both.
Tips & common pitfalls
- Copy secrets when shown. Keys and signing secrets appear exactly once. Lose one → revoke/delete and create a fresh one; nothing else re-reveals it.
- Answer webhooks fast with a 2xx. Do your real work after responding — a slow receiver hits the 10s timeout and looks like a failure.
- Don't redirect. Wraptor never follows a 3xx, so a redirect counts as a failed delivery. Register the final URL.
- Use
wraptor-idfor idempotency. Retries reuse the same id; process each id once. - Localhost and private-network URLs are rejected — receivers must be publicly reachable, over http or https.
- Deleting an endpoint stops its deliveries immediately; disabling keeps it listed so you can re-enable without re-sharing a secret.
Related modules
- Marketing — the Lead Inbox, where API-created leads land.
- Jobs — the stages behind
job.stage_changed. - Invoicing & Billing — the payments behind
invoice.paid. - Plans — what each tier includes.