- Events
- Listing webhooks
- Getting a webhook
- Creating a webhook
- Wildcard events
- Webhook signatures
- Updating a webhook
- Deleting a webhook
- Notifications
- Webhook headers
- Additional includes
Webhooks allow for 3rd parties to be notified when an event in Paymo occurs.
At the moment, the following events can be hooked:
- model.insert.Client
- model.update.Client
- model.delete.Client
- model.insert.ClientContact
- model.update.ClientContact
- model.delete.ClientContact
- model.insert.Project
- model.update.Project
- model.delete.Project
- model.insert.Tasklist
- model.update.Tasklist
- model.delete.Tasklist
- model.insert.Task
- model.update.Task
- model.delete.Task
- model.insert.Invoice
- model.update.Invoice
- model.delete.Invoice
- model.insert.InvoicePayment
- model.update.InvoicePayment
- model.delete.InvoicePayment
- model.insert.Entry
- model.update.Entry
- model.delete.Entry
- model.start.Entry
- model.stop.Entry
- model.insert.Milestone
- model.update.Milestone
- model.delete.Milestone
- model.insert.Report
- model.update.Report
- model.delete.Report
- model.insert.Expense
- model.update.Expense
- model.delete.Expense
- model.insert.Estimate
- model.update.Estimate
- model.delete.Estimate
- model.insert.Comment
- model.update.Comment
- model.delete.Comment
- model.insert.User
- model.update.User
- model.delete.User
- model.insert.Booking
- model.update.Booking
- model.delete.Booking
You can list your own webhooks by making a GET request to:
/api/hooks
Example of response:
{
"hooks": [
{
"id": 1,
"target_url": "https://myapp.com/paymo-insert-task-hook",
"last_status_code": null,
"event": "model.insert.Task",
"where": null,
"created_on": "2017-01-04T13:39:40Z",
"updated_on": "2017-01-04T13:39:40Z"
},
{
"id": 2,
"target_url": "https://myapp.com/paymo-delete-task-hook",
"last_status_code": null,
"event": "model.delete.Task",
"where": "project_id=123456",
"created_on": "2017-01-04T13:40:11Z",
"updated_on": "2017-01-04T13:40:11Z"
}
]
}
To get the webhook info, make a GET request to:
/api/hooks/[WEBHOOK_ID]
Example response:
{
"hooks": [
{
"id": 1,
"target_url": "https://myapp.com/paymo-insert-task-hook",
"last_status_code": 200,
"event": "model.insert.Task",
"where": null,
"created_on": "2017-01-04T13:39:40Z",
"updated_on": "2017-01-04T13:39:40Z"
}
]
}
To create a webhook, make a POST request to:
/api/hooks
with the request body containing the new webhook info as in the example below:
{
"target_url": "https://myapp.com/paymo/notifications",
"event": "model.insert.Task"
}
If successful, the response will contain the new webhook info.
By providing the where
param you can trigger webhooks only if the model matches the conditions from where
.
The where
param has the same syntax as when used in where
params of GET
requests. See response filtering.
For example, you want to be notified only for new task event in a specific project. The create request for this webhook will look like:
{
"target_url": "https://myapp.com/paymo/project-123-new-tasks",
"event": "model.insert.Task",
"where": "project_id=123"
}
When creating a webhook, you can also use a wildcard for the webhook event using the *
symbol.
For example, a webhook created with:
event
=*
will be triggered every time any event from the list occurs.event
=model.insert.*
will be triggered every time an insert event occurs.event
=*.Task
will be triggered every time an insert/update/delete event for a task occurs.
When using wildcard events, you can distinguish between event types by using the
X-Paymo-Event
header to get the actual event that triggered the webhook. (See headers)
Notice: When using where
with wildcard events, the validity of the where
param will not be
checked when creating the webhook. It will be checked against an actual event when the event will occur.
In case the where
param could not be parsed the webhook will not be triggered.
When creating a webhook you can also provide a secret
param.
In this case Paymo will sign this webhook requests so you can verify that they originated from Paymo.
This secret
value will not be returned when listing webhooks or getting webhook details.
These webhook triggers will contain a HTTP header X-Paymo-Signature
which is the
HMAC hex digest of the response body generated using the sha1
hash function and the secret
as the HMAC key.
For example, a delete project webhook created with secret
="secret" will have the header:
X-Paymo-Signature: sha1=dc03736e396e70138bf7af4ffaa2948cde42dcf1
for the body:
{"id":"1679584"}
To update an existing webhook, make a POST or PUT request to:
/api/hooks/[WEBHOOK_ID]
with the request body containing the updated info. You can send only the changed fields.
On update the webhook's last_response_code
will be reset.
Example of request body if you want to change the webhook target_url
:
{
"target_url": "https://myotherapp.com/notifications"
}
The response will return 200 OK
and will contain the updated webhook info.
By deleting a webhook, you will stop receiving notifications for the events specified in the webhook.
To delete a webhook, send a DELETE request to:
/api/hooks/[WEBHOOK_ID]
If successful, the response will have a 200 OK
status code.
A webhook is also deleted when target url
responds with a status code of 410 Gone
.
When an event occurs, Paymo will notify all webhooks that match the following criteria:
- webhook event matches occured event
- webhook was created by a user that has
access
rights to the object from the event
Paymo makes a webhook notification by making a POST request to the webhook URL, where the request
- has header
Content-type: application/json
- has body equal to a JSON representation of the object from the event.
For example, if the hook was created with:
{
"event": "model.insert.Task",
"target_url": "https://app.com/notifications"
}
when a task is created, and the user that created the hook has the rights to view the task, Paymo will make a POST request to https://app.com/notifications
with a body similar to:
{
"id":109403,
"name":"New Task",
"project_id":59032,
"tasklist_id":250019,
"user_id":1093,
"complete":false,
"billable":true,
"flat_billing":false,
"seq":1,
"description":"",
"price_per_hour":null,
"estimated_price":null,
"price":null,
"invoiced":false,
"invoice_item_id":null,
"due_date":null,
"start_date":null,
"budget_hours":null,
"users":[
1093
],
"created_on":"2016-09-29T09:46:55Z",
"updated_on":"2016-09-29T09:46:55Z",
"files_count":0,
"comments_count":0,
"project":{
"name":"AT&T Flyer Design"
},
"tasklist":{
"name":"Planning"
}
}
For insert
and update
events, the JSON object representation is mostly the same as the response you get by making a GET request to /api/tasks/[TASK_ID]
, except
- There is no
tasks
parent node in the response. The task object is at the root of JSON. - There are additional attributes like
project
andtasklist
, as in the GET request to/api/tasks/[TASK_ID]?include=project.name,tasklist.name
For delete
events, the notification content is a JSON object with a single attribute: the ID of the object that was deleted. For example:
{
"id": 109404
}
Any request by Paymo to a target URL triggered by a webhook will have additional headers:
X-Paymo-Webhook
with the ID of the webhook which is triggeredX-Paymo-Event
with the event that was triggered (e.g.model.insert.Task
)X-Paymo-Signature
with the HMAC hash of the request body when the webhook was created with asecret
Object type | Equivalent request |
---|---|
Client | /clients/[CLIENT_ID] |
Project | /projects/[PROJECT_ID]?include=client.name |
Task List | /tasklists/[TASKLIST_ID]?include=project.name |
Task | /tasks/[TASK_ID]?include=*,progress_status,project.name,tasklist.name |
Invoice | /invoices/[INVOICE_ID]?include=invoiceitems,client.name |
Time Entry | /entries/[ENTRY_ID]?include=task.name,user.name |
Milestone | /milestones/[MILESTONE_ID]?include=project.name |
Expense | /expenses/[EXPENSE_ID]?include=client.name,project.name |
Estimate | /estimates/[ESTIMATE_ID]?include=estimateitems,client.name |
Comment | /comments/[COMMENT_ID] |