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,PUTorPOSTto the same address. - Dates are ISO:
2027-04-15for calendar dates (due dates) and2026-11-09T14:30:00Z(UTC) for times. - Money is in cents:
45000is $450.00. - Lists come newest first, 25 per page (
?limit=up to 100). Ifhas_moreis true, ask again with?cursor=set to thenext_cursoryou 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."}}.
| HTTP | code | What it means |
|---|---|---|
| 400 | invalid_request | The body isn't valid JSON, or a parameter has the wrong format |
| 401 | unauthorized | No key, an unknown key or a revoked key |
| 403 | insufficient_scope | A read-only key tried to change something |
| 404 | not_found | No such record in your firm |
| 409 | conflict | For example a client ID that is already used |
| 422 | validation_error | A field is missing or not allowed; the message says which |
| 429 | rate_limited | Too many requests; wait and retry |
Endpoints
| Method and path | What it does |
|---|---|
| GET /me | Your firm and this key |
| GET /clients | List clients. Filters: q (name or client ID), code, email (a contact's email), tag, status (active, archived or all), updated_since |
| POST /clients | Create 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/ID | One client with its contacts |
| PATCH /clients/ID | Change name, code, type or tags (replaces all tags) |
| POST /clients/ID/tags | Add one tag: tag |
| DELETE /clients/ID/tags/TAG | Remove one tag |
| GET /contacts | List contacts. Filters: email, client_id, updated_since |
| POST /contacts | Create 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/ID | One contact with the clients they belong to |
| POST /contacts/ID/clients | Link a contact to a client: client_id, role |
| GET /pipelines | Your pipelines and their stages (you need the ids to create jobs) |
| GET /jobs | List jobs. Filters: client_id, pipeline_id, stage_id, assignee_id, status (active, done, archived or all) |
| POST /jobs | Create a job: client_id, pipeline_id (required), name, stage_id (default: first stage), due_date, assignee_id, priority (0, 1, 2) |
| GET /jobs/ID | One job |
| POST /jobs/ID/move | Move a job: stage_id, or stage with the stage name. The stage's automations run |
| GET /requests | Client requests (checklists). Filters: client_id, status (open, done, cancelled) |
| GET /requests/ID | One request with its items and whether each is done (no answers) |
| GET /invoices | Invoices. Filters: client_id, status (draft, open, partial, paid, void) |
| GET /invoices/ID | One invoice with its line items |
| GET /documents | Document details (name, type, size, folder, who uploaded it). Filters: client_id, created_since |
| GET /documents/ID | One document's details (never its contents) |
| GET /events | Every webhook event type |
| GET, POST /hooks | List or create webhook subscriptions (see Zapier, Make and n8n) |
| GET, DELETE /hooks/ID | Read or delete one subscription |
| GET /hooks/samples/EVENT | A 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.
| Event | When |
|---|---|
| client.created | A client was created |
| client.updated | A client's name, ID, type, tags or archived state changed |
| contact.created | A contact was added |
| document.uploaded | A document was added to a client (by the client, your team, a signature or the drop folder) |
| request.created | A client request was created |
| request.completed | The client finished every item of a request |
| organizer.submitted | The client submitted an organizer |
| signature.completed | Everyone signed and the signed PDF was sealed |
| approval.approved | The client approved a document |
| invoice.created | An invoice was created |
| invoice.sent | An invoice was sent to the client |
| invoice.paid | An invoice was paid in full |
| payment.received | A payment was recorded (card, bank, check or cash) |
| job.created | A job was created |
| job.stage_changed | A job moved to another stage (previous_stage shows where it came from) |
| proposal.accepted | The client accepted a proposal or engagement letter |
| message.received | A 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:
- Split the header on commas.
tis the time we signed it (Unix seconds);v1is the signature. - Take the raw request body exactly as received, before parsing the JSON.
- Compute HMAC-SHA256 with your signing secret as the key over the text
t+.+ raw body. Write it as lowercase hex. - Compare it with
v1using a constant-time comparison. Also reject the request iftis 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/hookswith{"target_url": "https://...", "event": "invoice.paid"}(or"events": [...]). The answer contains the subscriptionidand its signingsecret. A read and write key is needed. - Unsubscribe:
DELETE /api/v1/hooks/ID. - Sample data for field mapping:
GET /api/v1/hooks/samples/invoice.paidreturns a list with one example event. - Authentication test:
GET /api/v1/me.
Recipe: a Calendly booking creates a client
- Trigger: Calendly → Invitee Created.
- Optional step to avoid duplicates: Custom Request
GET https://app.taxporch.com/api/v1/clients?email=plus the invitee's email. Continue only ifdatais empty (Zapier: Filter). - Action: Custom Request
POST https://app.taxporch.com/api/v1/clientswith headerAuthorization: Bearer tpa_...and a JSON body withname(the invitee's name),email,first_name,last_nameandtags(for example["New lead"]). - Optional: create a job right away with
POST /api/v1/jobsusing the new client'sidand apipeline_idfromGET /api/v1/pipelines.
Recipe: paid invoices go to a Google Sheets row
- In Zapier, create a Zap with Webhooks by Zapier → Catch Hook and copy the URL.
- In Taxporch, add a webhook with that URL and tick invoice.paid. Click Send test event, or pay a small test invoice.
- Action: Google Sheets → Create Spreadsheet Row. Map
data.number,data.client.name,data.total_cents(divide by 100 in a Formatter step),data.paid_atanddata.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.