API, webhooks and calendar feeds

Taxporch has a REST API, signed webhooks and private calendar feeds. Use them to connect Zapier, Make, n8n, Google Sheets, your booking tool or your own scripts. Everything on this page is included in every plan.

What you can do

  • Read clients, contacts, jobs, pipelines, client requests, invoices and document details.
  • Create clients, contacts and jobs, link contacts to clients, add or remove tags, and move jobs to another stage. These changes go through the same steps as the app: they appear in your audit log, and stage automations run as if someone on your team made the change.
  • Get told when something happens (a new client, an uploaded document, a paid invoice, a signed Form 8879 and more) with webhooks.
  • See due dates of requests, organizers, invoices and jobs in Google Calendar, Outlook or Apple Calendar.

What the API never returns: tax organizer answers, Social Security numbers or other sensitive answers, request answers, message text, or the contents of any document. Document endpoints return names, sizes and folders only. Each key only ever sees your own firm.

API keys

An owner or admin creates keys under Settings → Integrations → Taxporch API (or go straight to API and webhooks).

  • Give the key a name (for example "Zapier") so you know what to revoke later.
  • Choose Read only or Read and write. Read-only keys can only use GET. Use read and write only where you need to create or change things.
  • The key starts with tpa_ and is shown once. We store only a fingerprint of it, so we can't show it again. If you lose it, revoke it and create a new one.
  • The list shows when each key was last used. Revoke a key and anything using it stops working at once.
  • A key stops working if the admin who created it leaves the firm or is no longer an admin.

Send the key in the Authorization header of every request: Authorization: Bearer tpa_your_key. Test it with GET https://app.taxporch.com/api/v1/me, which returns your firm name and the key's access level.

Each key can make 5 requests per second, with short bursts up to 120. Above that you get HTTP 429 with a Retry-After header; wait a second and try again.

Requests and responses

  • Base URL: https://app.taxporch.com/api/v1
  • Send JSON (Content-Type: application/json). Form fields work too, which helps with simple tools.
  • Updates accept PATCH, PUT or POST to the same address.
  • Dates are ISO: 2027-04-15 for calendar dates (due dates) and 2026-11-09T14:30:00Z (UTC) for times.
  • Money is in cents: 45000 is $450.00.
  • Lists come newest first, 25 per page (?limit= up to 100). If has_more is true, ask again with ?cursor= set to the next_cursor you got.

A list looks like {"object": "list", "data": [...], "has_more": true, "next_cursor": "812"}.

Errors always have the same shape: {"error": {"code": "validation_error", "message": "name is required."}}.

HTTPcodeWhat it means
400invalid_requestThe body isn't valid JSON, or a parameter has the wrong format
401unauthorizedNo key, an unknown key or a revoked key
403insufficient_scopeA read-only key tried to change something
404not_foundNo such record in your firm
409conflictFor example a client ID that is already used
422validation_errorA field is missing or not allowed; the message says which
429rate_limitedToo many requests; wait and retry

Endpoints

Method and pathWhat it does
GET /meYour firm and this key
GET /clientsList clients. Filters: q (name or client ID), code, email (a contact's email), tag, status (active, archived or all), updated_since
POST /clientsCreate a client. name (required), type (individual, company, other), code, tags, and optionally a primary contact: email, first_name, last_name, phone (or the same inside contact)
GET /clients/IDOne client with its contacts
PATCH /clients/IDChange name, code, type or tags (replaces all tags)
POST /clients/ID/tagsAdd one tag: tag
DELETE /clients/ID/tags/TAGRemove one tag
GET /contactsList contacts. Filters: email, client_id, updated_since
POST /contactsCreate a contact: first_name, last_name, email, phone, and optionally client_id and role (primary, spouse, member). If a contact with that email exists, it is reused and linked (HTTP 200 and "created": false)
GET /contacts/IDOne contact with the clients they belong to
POST /contacts/ID/clientsLink a contact to a client: client_id, role
GET /pipelinesYour pipelines and their stages (you need the ids to create jobs)
GET /jobsList jobs. Filters: client_id, pipeline_id, stage_id, assignee_id, status (active, done, archived or all)
POST /jobsCreate a job: client_id, pipeline_id (required), name, stage_id (default: first stage), due_date, assignee_id, priority (0, 1, 2)
GET /jobs/IDOne job
POST /jobs/ID/moveMove a job: stage_id, or stage with the stage name. The stage's automations run
GET /requestsClient requests (checklists). Filters: client_id, status (open, done, cancelled)
GET /requests/IDOne request with its items and whether each is done (no answers)
GET /invoicesInvoices. Filters: client_id, status (draft, open, partial, paid, void)
GET /invoices/IDOne invoice with its line items
GET /documentsDocument details (name, type, size, folder, who uploaded it). Filters: client_id, created_since
GET /documents/IDOne document's details (never its contents)
GET /eventsEvery webhook event type
GET, POST /hooksList or create webhook subscriptions (see Zapier, Make and n8n)
GET, DELETE /hooks/IDRead or delete one subscription
GET /hooks/samples/EVENTA sample event, for setting up fields in Zapier or Make

Uploading files is done with the drop-folder upload key, not an API key: see the drop folder guide in Settings → Drop folder.

Webhooks

A webhook sends an HTTPS POST with a small JSON body to your URL each time an event happens. Add one under Settings → Integrations → Webhooks: paste the URL, tick the events you want, and save. Then use Send test event to check that it arrives.

The URL must start with https:// and point to the public internet. Addresses inside private networks (like 10.x, 192.168.x, localhost or cloud metadata addresses) are refused, both when you save and every time we send.

EventWhen
client.createdA client was created
client.updatedA client's name, ID, type, tags or archived state changed
contact.createdA contact was added
document.uploadedA document was added to a client (by the client, your team, a signature or the drop folder)
request.createdA client request was created
request.completedThe client finished every item of a request
organizer.submittedThe client submitted an organizer
signature.completedEveryone signed and the signed PDF was sealed
approval.approvedThe client approved a document
invoice.createdAn invoice was created
invoice.sentAn invoice was sent to the client
invoice.paidAn invoice was paid in full
payment.receivedA payment was recorded (card, bank, check or cash)
job.createdA job was created
job.stage_changedA job moved to another stage (previous_stage shows where it came from)
proposal.acceptedThe client accepted a proposal or engagement letter
message.receivedA client sent a portal message (the text itself is not included)

Every event has the same envelope: {"id": "evt_812_client_created", "type": "client.created", "created": "2026-11-09T14:30:00Z", "firm_id": 7, "data": {...}}. The data object is a small summary with ids, names, status and a link back into Taxporch. Fetch more with the API if you need it. The id stays the same if we send the same event again, so you can ignore duplicates.

Each request also carries the headers Taxporch-Event (the type), Taxporch-Event-Id, Taxporch-Delivery and Taxporch-Signature.

Retries and turning off

  • Answer with any 2xx status within 10 seconds. Do slow work after you answer.
  • If your endpoint fails or times out, we try again after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours.
  • If all six attempts fail, the webhook is turned off, every owner and admin gets an email, and the Integrations page shows Needs attention. Fix the endpoint and click Turn on.
  • If your endpoint answers 410 Gone, we turn the webhook off right away (this is how Zapier says a Zap was deleted).
  • The webhook's page shows the last 50 deliveries with the HTTP status your endpoint returned.

Verifying signatures

Every webhook has its own signing secret (it starts with whsec_). You see it on the webhook's page, and you can replace it with New signing secret.

The Taxporch-Signature header looks like t=1762698600,v1=5f2c.... To check it:

  1. Split the header on commas. t is the time we signed it (Unix seconds); v1 is the signature.
  2. Take the raw request body exactly as received, before parsing the JSON.
  3. Compute HMAC-SHA256 with your signing secret as the key over the text t + . + raw body. Write it as lowercase hex.
  4. Compare it with v1 using a constant-time comparison. Also reject the request if t is more than 5 minutes old, so an old request can't be replayed.

In Python that is: hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest(). In Node.js: crypto.createHmac("sha256", secret).update(t + "." + rawBody).digest("hex").

Zapier, Make and n8n

The simplest setup needs no code: use the tool's own webhook trigger and the API key for actions.

  • Zapier: for triggers, use Webhooks by Zapier → Catch Hook, copy the hook URL into a new Taxporch webhook and tick the events. For actions, use Webhooks by Zapier → Custom Request (or POST) with the header Authorization: Bearer tpa_....
  • Make: for triggers, use Webhooks → Custom webhook and paste its address into a Taxporch webhook. For actions, use HTTP → Make a request with the same header.
  • n8n: use the Webhook node for triggers and the HTTP Request node (header auth) for actions.

Building a private Zapier app, or an n8n or Make app with a real trigger list? Taxporch supports REST hooks:

  • Subscribe: POST /api/v1/hooks with {"target_url": "https://...", "event": "invoice.paid"} (or "events": [...]). The answer contains the subscription id and its signing secret. A read and write key is needed.
  • Unsubscribe: DELETE /api/v1/hooks/ID.
  • Sample data for field mapping: GET /api/v1/hooks/samples/invoice.paid returns a list with one example event.
  • Authentication test: GET /api/v1/me.

Recipe: a Calendly booking creates a client

  1. Trigger: Calendly → Invitee Created.
  2. Optional step to avoid duplicates: Custom Request GET https://app.taxporch.com/api/v1/clients?email= plus the invitee's email. Continue only if data is empty (Zapier: Filter).
  3. Action: Custom Request POST https://app.taxporch.com/api/v1/clients with header Authorization: Bearer tpa_... and a JSON body with name (the invitee's name), email, first_name, last_name and tags (for example ["New lead"]).
  4. Optional: create a job right away with POST /api/v1/jobs using the new client's id and a pipeline_id from GET /api/v1/pipelines.

Recipe: paid invoices go to a Google Sheets row

  1. In Zapier, create a Zap with Webhooks by Zapier → Catch Hook and copy the URL.
  2. In Taxporch, add a webhook with that URL and tick invoice.paid. Click Send test event, or pay a small test invoice.
  3. Action: Google Sheets → Create Spreadsheet Row. Map data.number, data.client.name, data.total_cents (divide by 100 in a Formatter step), data.paid_at and data.url.

More ideas

  • document.uploaded → post a message in your team's Slack or Teams channel.
  • signature.completed → move the job forward or tick a box in your tracking sheet.
  • job.stage_changed to "Ready for review" → send a text to the reviewer.
  • client.created → add the client to your newsletter tool (only with the client's consent).

Calendar feeds

Every team member can add a private calendar link under Settings → Integrations → Calendar feed. It shows all-day events for the due dates of open requests, organizers, invoices and jobs of the clients that person can see. Staff who only see some clients only get those clients' dates.

  • Google Calendar: Other calendars → + → From URL. Google refreshes about every 12 hours.
  • Outlook: Add calendar → Subscribe from web.
  • Apple Calendar: File → New Calendar Subscription (set auto-refresh to every hour). On iPhone: Settings → Calendar → Accounts → Add Account → Other → Add Subscribed Calendar.

The link works without signing in, so keep it private. Only titles, client names and dates are in it. Click Create a new link to replace it (the old one stops working) or Turn off to remove it.

Questions

Write to support@taxporch.com. Tell us what you want to connect; we add endpoints and events based on what firms ask for.