Integrations — automate dispatch and billing
Connect Pyles Ops to Zapier, n8n, Make or Pipedream to auto-assign drivers the moment a delivery lands, and to keep invoices, payments and driver payouts in sync with your accounting stack. Every example below is copy-paste ready.
How the connection works
Outbound: we call your automation
Paste a platform webhook URL into your automation settings. Pyles OpsPOSTs a JSON event whenever a delivery, payout or invoice changes.
{
"event": "delivery.created",
"delivery_id": "DL-10482",
"client": "Northside Grocers",
"pickup": { "lat": 33.7756, "lng": -84.3963, "address": "Midtown Hub, Atlanta GA" },
"dropoff": { "lat": 33.9304, "lng": -84.3733, "address": "Sandy Springs, GA" },
"courier_needs": ["refrigerated", "liftgate"],
"value_usd": 214.5,
"window": { "start": "2026-08-08T14:00:00Z", "end": "2026-08-08T17:00:00Z" }
}Inbound: your automation calls us
Send an authenticated POST with the X-Api-Key header to trigger auto-assignment or to acknowledge a billing sync.
{
"assigned_driver": { "id": "DRV-27", "name": "Driver 27", "match_score": 0.91 },
"addons": [{ "code": "COLD_CHAIN", "revenue_usd": 35, "driver_payout_usd": 12.25 }],
"projected_margin_usd": 88.4
}Events are retried up to 3 times with exponential backoff. Treat every handler as idempotent and key it on delivery_id or invoice_id.
Platform guides
Fastest no-code path. Best when your team already lives in Zapier.
Workflow 1 — Auto-assign a new delivery
- Trigger: Webhooks by Zapier → Catch Hook. Copy the hook URL into your Pyles Ops automation settings.
- Filter: only continue if
courier_needsis not empty. - Action: Code by Zapier with the snippet below (or a plain Webhooks POST).
- Action: Slack / SMS to notify the matched driver.
// Zapier "Code by Zapier" step (JavaScript) — auto-assign
const res = await fetch("https://dashpilot-fleetflow.lovable.app/api/public/dispatch/auto-assign", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Api-Key": inputData.plyesOpsApiKey,
},
body: JSON.stringify({
delivery_id: inputData.deliveryId,
courier_needs: (inputData.needs || "").split(","),
value_usd: Number(inputData.value),
}),
});
const data = await res.json();
output = [{ driverId: data.assigned_driver?.id, score: data.assigned_driver?.match_score }];Workflow 2 — Billing sync
- Trigger: Catch Hook on invoice events.
- Paths: paid → create a payment in QuickBooks/Xero; overdue → send an AR reminder email.
- Action: append a row to a Google Sheet ledger for reconciliation.
// Sending a Pyles Ops event into a Zap (Catch Hook trigger)
await fetch(zapierWebhookUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
mode: "no-cors",
body: JSON.stringify({
timestamp: new Date().toISOString(),
triggered_from: window.location.origin,
event: "invoice.status_changed",
invoice_id: "INV-2026-0413",
}),
});Browser-side posts use no-cors, so the response is opaque — confirm delivery in the Zap's run history.
Webhook setup
Create a receiving URL on your platform, paste it into Pyles Ops, pick the events you want, then verify the signature on every request. The same endpoint can receive both auto-assign and billing events — branch on event.
Step 1 — Create the receiving URL
| Platform | Trigger module | URL shape | Gotcha |
|---|---|---|---|
| Zapier | Webhooks by Zapier → Catch Raw Hook | https://hooks.zapier.com/hooks/catch/123456/abcdef/ | Use Catch RAW Hook (not Catch Hook) so the unparsed body is available for signature verification. |
| n8n | Webhook node (POST, Response mode: Immediately) | https://your-n8n.app/webhook/pyles-ops | Enable Raw Body in node options; the test URL only listens while the canvas is open — use the Production URL. |
| Make | Webhooks → Custom webhook | https://hook.us1.make.com/abc123xyz | Click Re-determine data structure and send one sample event so Make maps the nested data object. |
| Pipedream | HTTP / Webhook Requests (Return a static response) | https://abc123.m.pipedream.net | Read the raw body from steps.trigger.event.bodyRaw; store the signing secret as a secret prop. |
Step 2 — Register the endpoint in Pyles Ops
- Open More → Integrations → Webhook endpoints and choose Add endpoint.
- Paste the URL, then subscribe to
delivery.createdandassignment.completedfor auto-assign, andinvoice.status_changedpluspayout.updatedfor billing sync. - Copy the generated signing secret into your automation platform's secret store — it is shown once.
- Use Send test event so the platform can map the payload structure before you go live.
Step 3 — Request format and headers
POST /hooks/pyles-ops HTTP/1.1
Content-Type: application/json
User-Agent: PylesOps-Webhooks/1
X-Pyles-Event: assignment.completed
X-Pyles-Delivery-Id: whd_01J9Z3K7QF2M # unique per attempt
X-Pyles-Event-Id: evt_01J9Z3K7QF2M # stable across retries — dedupe on this
X-Pyles-Timestamp: 1786331051 # unix seconds
X-Pyles-Signature: v1=9f2c...c41b # hex HMAC-SHA256 of "{timestamp}.{rawBody}"Step 4 — Verify the signature
// Verify X-Pyles-Signature before trusting a payload (Node.js / Pipedream / n8n Code node)
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyPylesWebhook(rawBody, headers, secret) {
const timestamp = headers["x-pyles-timestamp"];
const received = String(headers["x-pyles-signature"] || "").replace(/^v1=/, "");
// Reject anything older than 5 minutes to block replays
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected = createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const a = Buffer.from(received, "hex");
const b = Buffer.from(expected, "hex");
return a.length === b.length && timingSafeEqual(a, b);
}In Zapier use a Code by Zapier step on a Catch Raw Hook; in Make add a Tools → Set variable with the sha256 HMAC function; in n8n and Pipedream paste the function above directly into a code node.
Sample payload — auto-assign
assignment.completed fires once a driver is matched, with the score breakdown, billable add-ons and projected margin your workflow needs to route, notify and log.
{
"event": "assignment.completed",
"event_id": "evt_01J9Z3K7QF2M",
"occurred_at": "2026-08-08T14:04:11Z",
"environment": "live",
"data": {
"delivery_id": "DL-10482",
"client": { "id": "CL-118", "name": "Northside Grocers", "referred": true },
"pickup": { "lat": 33.7756, "lng": -84.3963, "address": "Midtown Hub, Atlanta GA" },
"dropoff": { "lat": 33.9304, "lng": -84.3733, "address": "Sandy Springs, GA" },
"courier_needs": ["refrigerated", "liftgate"],
"assignment": {
"mode": "auto",
"driver": { "id": "DRV-27", "name": "Driver 27", "phone": "+1404555••27" },
"match_score": 0.91,
"score_breakdown": {
"capability_match": 0.40,
"proximity": 0.27,
"availability": 0.14,
"on_time_history": 0.10
},
"eta_to_pickup_min": 12,
"runner_up_driver_ids": ["DRV-14", "DRV-31"]
},
"addons": [
{ "code": "COLD_CHAIN", "label": "Refrigerated handling", "revenue_usd": 35.00, "driver_payout_usd": 12.25 },
{ "code": "LIFTGATE", "label": "Liftgate service", "revenue_usd": 20.00, "driver_payout_usd": 7.00 }
],
"economics": {
"base_revenue_usd": 214.50,
"addon_revenue_usd": 55.00,
"driver_payout_usd": 118.85,
"projected_margin_usd": 88.40,
"margin_pct": 32.8
}
}
}Sample payload — billing sync
invoice.status_changed carries before/after states, the line items behind the total and the queued driver payouts to release once your ledger confirms the payment.
{
"event": "invoice.status_changed",
"event_id": "evt_01J9ZB4T8HD3",
"occurred_at": "2026-08-08T18:04:11Z",
"environment": "live",
"data": {
"invoice_id": "INV-2026-0413",
"client": { "id": "CL-118", "name": "Northside Grocers", "terms": "net_15" },
"period": { "start": "2026-07-01", "end": "2026-07-31" },
"before": { "status": "sent", "total_usd": 1840.00, "amount_paid_usd": 0 },
"after": {
"status": "paid",
"total_usd": 1840.00,
"amount_paid_usd": 1840.00,
"paid_at": "2026-08-08T18:04:11Z",
"payment_reference": "pay_9f31c2"
},
"line_items": [
{ "delivery_id": "DL-10482", "description": "Refrigerated run — Sandy Springs", "amount_usd": 269.50 },
{ "delivery_id": "DL-10488", "description": "Standard run — Decatur", "amount_usd": 142.00 }
],
"driver_payouts": [
{ "driver_id": "DRV-27", "amount_usd": 612.50, "status": "queued", "release_on": "2026-08-09" },
{ "driver_id": "DRV-14", "amount_usd": 288.75, "status": "queued", "release_on": "2026-08-09" }
],
"totals": { "revenue_usd": 1840.00, "driver_payout_usd": 901.25, "net_margin_usd": 938.75 }
}
}Step 5 — Handle retries and ordering
Attempt 1 immediate
Attempt 2 +30s
Attempt 3 +2m
Attempt 4 +10m (final)
• Any 2xx = delivered. 410 = we disable the endpoint.
• Timeout is 10s — acknowledge fast, process asynchronously.
• X-Pyles-Event-Id is stable across retries; X-Pyles-Delivery-Id is not.
• Out-of-order delivery is possible: compare occurred_at before overwriting state.Event reference
| Event | Fires when | Typical use |
|---|---|---|
| delivery.created | A new delivery enters the dispatch feed | Auto-assign the best-matched driver |
| delivery.status_changed | Pickup, en route, delivered or exception | Customer ETA notifications |
| assignment.completed | A driver accepts or auto-assign resolves | Update your TMS or Slack channel |
| payout.updated | Driver payout components recalculate | Push to payroll |
| invoice.status_changed | Invoice sent, paid, overdue or void | Billing sync to accounting |
| opportunity.status_changed | Referral, upsell or training progresses | Commission tracking |
Security checklist
- Keep API keys in your automation platform's secret store — never in a step's visible body.
- Verify the signature header on inbound events before acting on them.
- Scope each automation to one job so a leaked key has a limited blast radius.
- Rotate keys when a teammate with automation access leaves.