Connect your CRM

Send finished galleries to your CRM with Zapier or Make, or use the API directly.

Zapier

  1. Create a Zap. For the trigger, choose Webhooks by Zapier, then Catch Hook. Copy the URL it gives you.
  2. In JobProof, open Settings, paste it under Connect your CRM, and tap Send test.
  3. Back in Zapier, test the trigger. Add an action for your CRM, like a note on the customer with job.shareUrl.
  4. Filter on event equals gallery.ready if you only want finished galleries.

Make

Add a Webhooks module with Custom webhook, copy its URL into Settings the same way, and tap Send test so Make can learn the data structure.

Events

JobProof sends a JSON POST to your URL. Failed deliveries are retried for about an hour.

gallery.ready

The job has a client link and every photo has uploaded. Sent once per link.

{
  "id": "6d0e…",
  "event": "gallery.ready",
  "occurredAt": "2026-11-01T17:30:00.000Z",
  "business": "Northside Interiors",
  "job": {
    "id": "b1f2…",
    "title": "Sarah Miller, reception fit-out",
    "address": "12 Oak Lane",
    "externalId": "crm-77",
    "shareUrl": "https://…/s/…",
    "photoCount": 24,
    "uploadedCount": 24,
    "createdAt": 1793000000000,
    "updatedAt": 1793000500000
  }
}

job.completed

Sent when someone marks a job complete. The job object has the same fields as below, and every event's job also includes status (open or completed), dueAt, startedAt, completedAt, location, assignedTo and completedBy. Names are team member names.

job.created

A job was created on a phone. Same shape. Jobs created through the API don't send this event.

webhook.test

Sent by the Send test button. job is null.

Verifying signatures

Each request has an X-JobProof-Signature: t=<ms>,v1=<hex> header. v1 is the HMAC-SHA256 of t + "." + rawBody, keyed with the signing secret in Settings. Retries reuse the same event id, so use it to ignore duplicates.

API

Create a key in Settings and send it as Authorization: Bearer jp_…. Keys can read and create jobs and manage client links. They can't reach photos or your account.

Create a job

It appears on your phones at their next sync. With externalId, repeat calls return the existing job, so a retrying Zap won't make duplicates.

POST /api/v1/jobs
{ "title": "Lopez patio", "address": "9 Elm St", "notes": "Side gate", "externalId": "crm-77" }

201 { "job": { "id": "…", … }, "created": true }
200 { "job": { "id": "…", … }, "created": false }

List jobs

GET /api/v1/jobs?updatedSince=<ms>&limit=50

{ "jobs": [ … ] }

Get a job

GET /api/v1/jobs/:id

Turn a client link on or off

POST /api/v1/jobs/:id/share     → { "shareUrl": "https://…/s/…" }
DELETE /api/v1/jobs/:id/share   → { "shareUrl": null }

Turning a link off stops it working at once. Turning it on again makes a new link.

Get a job's photos

GET /api/v1/jobs/:id/photos

{ "shareUrl": "https://…/s/…", "notes": "…",
  "photos": [ { "id": "…", "role": "before", "caption": "…",
                "full": "https://…/s/…/…/full", "thumb": "https://…/s/…/…/thumb" } ] }

Returns uploaded photos that aren't hidden. Photo addresses work through the client link, so a job with no link gets one. A link somebody turned off stays off: shareUrl is null and photos is empty.

job.completed can arrive before every photo has uploaded. To get the full set, pull photos on gallery.ready as well.