API & Webhooks
#Overview
Connect Matterly to your ERP, your CRM (Perfex and others), Zapier, Make or an AI assistant. Two parts:
- The API: other systems read and create clients, matters, invoices and Smart Form responses, and send Smart
Forms for signature.
- Webhooks: Matterly tells other systems when something happens (a new client, a paid invoice, a fully signed
form…).
Both are in Settings → API and need API access in your plan.
#API keys
Create one key per integration and give it only the permissions it needs.
| Permission |
Allows |
matters:read / matters:write |
List and read matters / create and cancel them |
clients:read / clients:write |
List and read clients / create and update them |
invoices:read |
List and read client invoices |
forms:read |
List Smart Forms, read responses (answers, signers, extras) and their PDF |
forms:send |
Send a Smart Form, including for signature by several people |
A key is shown once, when you create it. Matterly keeps only a fingerprint of it. Revoke stops it at once;
the list shows when each key was last used.
Send the key with every request, either as X-Api-Key: sp_… or Authorization: Bearer sp_…. Each key may make 120
requests a minute.
#Endpoints
All addresses start with https://your-site/api/v1/firms/{your-firm}. Lists return 50 records per page with a
next_page number, and take ?updated_since=2026-10-01T00:00:00Z to fetch only what changed.
| Method and path |
Permission |
GET /clients (?email=), GET /clients/{id} |
clients:read |
POST /clients, PATCH /clients/{id} |
clients:write |
GET /matters (?status=, ?client_id=), GET /matters/{number} |
matters:read |
POST /matters, DELETE /matters/{number} |
matters:write |
GET /invoices (?status=), GET /invoices/{number} |
invoices:read |
GET /smart-forms |
forms:read |
GET /smart-form-submissions (?form_id=), GET /smart-form-submissions/{id}, GET …/{id}/pdf |
forms:read |
POST /smart-forms/{id}/send |
forms:send |
GET /availability, GET /practice-areas |
none until you create your first key, then any key |
Creating a client with an email that already exists returns that client instead of a duplicate.
Send a form for signature (forms signed by several people):
POST /smart-forms/12/send
{
"signers": {
"owner": { "name": "Asha Rao", "email": "asha@example.com" },
"co_owner": { "name": "Vikram Rao", "email": "vikram@example.com" },
"witness": { "name": "Meera Iyer" }
},
"matter_number": "MAT-2026-0042"
}
The answer includes signer_links, one per role. For other forms, send {"email": "client@example.com"}.
#Webhooks
Add an address and choose the events it receives:
| Event |
Sent when |
client.created |
A client is added |
matter.created |
A matter is opened |
invoice.paid |
A client invoice becomes paid |
smart_form.submitted |
A Smart Form is submitted on its public link |
smart_form.completed |
Everyone has signed and the sealed PDF is stored |
smart_form.declined |
A signer declines |
form_order.paid |
Extras bought while signing are paid |
Each message is a POST with a small JSON body that carries references only, never names, emails or answers:
{
"id": "6f1c…",
"event": "smart_form.completed",
"created_at": "2026-10-14T09:12:00+00:00",
"firm": "sterling-whitfield",
"data": { "submission_id": 381, "form_id": 12, "sealed_sha256": "85a6…" }
}
Fetch the details with an API key (here GET /smart-form-submissions/381).
Check the signature. Each request has X-Matterly-Timestamp and X-Matterly-Signature. The signature is
sha256= followed by the HMAC-SHA256 of {timestamp}.{raw body} with the webhook's secret, which is shown once when
you add the webhook:
$expected = 'sha256='.hash_hmac('sha256', $_SERVER['HTTP_X_MATTERLY_TIMESTAMP'].'.'.file_get_contents('php://input'), $secret);
$valid = hash_equals($expected, $_SERVER['HTTP_X_MATTERLY_SIGNATURE'] ?? '');
Retries. Answer with any 2xx status. Otherwise Matterly tries again after 1, 5, 30, 120 and 720 minutes. A
webhook that fails 50 times in a row is switched off and firm admins are notified; switch it back on once it is
fixed. Send test posts a ping, and each webhook shows its recent deliveries with Resend.
On a live site, webhook addresses must use https:// and point to a public internet address.
#Recipe: Perfex CRM (or another ERP)
Perfex's own REST API comes from an add-on for Perfex, and the field names below depend on that add-on. The pattern
is the same for any CRM or ERP.
Matterly → Perfex (new clients and signed documents):
- In Matterly, create a key with
clients:read and forms:read, and a webhook to your receiver (a small script on
your server, or a Zapier or Make "catch hook") for client.created and smart_form.completed.
- On
client.created, call GET /clients/{client_id} and create the customer in Perfex with its name, email and
phone.
- On
smart_form.completed, call GET /smart-form-submissions/{submission_id} for the answers and
GET …/pdf for the signed document, and attach it to the Perfex customer.
Perfex → Matterly (customers created in Perfex):
- Create a key with
clients:write.
- When a customer is added in Perfex (a Perfex hook, or Zapier/Make watching Perfex), call
POST /clients. Because
the email is matched, sending the same customer twice does not create a duplicate.
Keep both directions keyed on the email address, so the two systems never fight over which record is the original.
#AI assistants (MCP)
The MCP server add-on lets Claude, Cursor and other MCP clients use the same keys and permissions.
See MCP server.