zmoog / toggl-bridge

Forwards Toggl webhooks to other destinations
MIT License
0 stars 0 forks source link

Document how Toggl webhooks work #1

Open zmoog opened 1 year ago

zmoog commented 1 year ago

Research how Toggl webhooks work to build a bridge. The end goal is to collect data to run analytics on my peculiar way to use time tracking.

Starting point: https://developers.track.toggl.com/docs/webhooks_start

zmoog commented 1 year ago

The ping

Toggl sends a ping HTTP request to validate the endpoin;: here is an example:

Headers

Host: enoee7ejthhcr.x.pipedream.net
X-Amzn-Trace-Id: Root=1-63cbba4f-5facd01376c5856720b4efa4
Content-Length: 351
content-type: application/json
x-webhook-signature-256: sha256=8631c55075f1c7c9a
accept-encoding: gzip
user-agent: Go-http-client/2.0

Body

{
    "event_id": 0,
    "created_at": "2023-01-21T10:11:27.53461377Z",
    "creator_id": 2621333,
    "metadata": {
        "request_type": "POST",
        "event_user_id": 2621333
    },
    "payload": "ping",
    "subscription_id": 8,
    "validation_code": "0f0d7013",
    "validation_code_url": "https://track.toggl.com/webhooks/api/v1/validate/118/4438/0f0"
}
zmoog commented 1 year ago

Next step is URL Endpoint Validation.

zmoog commented 1 year ago

We have two validation options:

In he synchronous validation option the target HTTP endpoint is responsible to return back a response with {"validation_code": "<validation_code>"} using the same validation_code value sent in the ping request.

In the asynchronous option, we can send a simple GET request to a dedicated validate endpoint. If the endpoint answers with OK the validation is complete.

zmoog commented 1 year ago

I can continue using requestbin.com to test the webhooks before writing any code.

zmoog commented 1 year ago

The plan is:

Create a request bin to receive webhooks

I registered to https://requestbin.com and created a private bin for this exploration.

CleanShot 2023-01-22 at 07 20 46@2x

Customize the JS step to return the validation_code sent in the HTTP request:

// To return a custom HTTP response, use $.respond() [requires HTTP trigger]
export default defineComponent({
  async run({ steps, $ }) {
    await $.respond({
      status: 200,
      headers: {
        "content-type": "application/json"
      },
      body: JSON.stringify({
        "validation_code": steps.trigger.event.body.validation_code
      }),
    })
  },
})

Trying to send the sample payload I collected before:

curl -H "Content-Type: application/json" -X POST -d '{ "event_id": 0, "created_at": "2023-01-21T10:11:27.53461377Z", "creator_id": 456, "metadata": { "request_type": "POST", "event_user_id": 456 }, "payload": "ping", "subscription_id": 8, "validation_code": "0f0d7013", "validation_code_url": "https://track.toggl.com/webhooks/api/v1/validate/118/4438/0f0" }' https://eo4sf6wa6nmc4w.m.pipedream.net
{"validation_code":"0f0d7013"}%                                                                                            

Voilà, we are ready to go to the next step.

Create and validate a new webhook for timer events

Created a new webhook to forward the events to the request bin:

CleanShot 2023-01-22 at 07 24 56@2x

Start and stop some timer entries to collect real world sample data

Started a new time entry:

{
  "event_id": 6680087045722268,
  "created_at": "2023-01-22T06:12:22.929Z",
  "creator_id": 456,
  "metadata": {
    "action": "created",
    "event_user_id": "456",
    "model": "time_entry",
    "model_owner_id": "456",
    "path": "/api/v9/time_entries",
    "project_id": "789",
    "project_is_private": "true",
    "request_body": "{\"created_with\":\"Snowball\",\"pid\":789,\"tid\":null,\"billable\":false,\"description\":\"toggl bridge\",\"tags\":[],\"wid\":987,\"at\":\"2023-01-22T06:12:22.382Z\",\"start\":\"2023-01-22T06:12:22.000Z\",\"duration\":-1674367942}",
    "request_type": "POST",
    "time_entry_id": "123",
    "workspace_id": "987"
  },
  "payload": {
    "at": "2023-01-22T06:12:22+00:00",
    "billable": false,
    "description": "toggl bridge",
    "duration": -1674367942,
    "duronly": true,
    "id": 123,
    "pid": 789,
    "project_id": 789,
    "server_deleted_at": null,
    "start": "2023-01-22T06:12:22Z",
    "stop": null,
    "tag_ids": null,
    "tags": [],
    "task_id": null,
    "uid": 456,
    "user_id": 456,
    "wid": 987,
    "workspace_id": 987
  },
  "subscription_id": 4438
}

Stopped a running time entry:

{
  "event_id": 6680126864605492,
  "created_at": "2023-01-22T06:17:23.879Z",
  "creator_id": 456,
  "metadata": {
    "action": "updated",
    "event_user_id": "456",
    "model": "time_entry",
    "model_owner_id": "456",
    "path": "/api/v9/time_entries/123",
    "project_id": "789",
    "project_is_private": "true",
    "request_body": "{\"id\":123,\"billable\":false,\"start\":\"2023-01-22T06:12:22.000Z\",\"stop\":\"2023-01-22T06:17:23.000Z\",\"duration\":301,\"description\":\"toggl bridge\",\"tags\":[],\"duronly\":true,\"at\":\"2023-01-22T06:12:22.775281Z\",\"server_deleted_at\":null,\"user_id\":456,\"uid\":456,\"wid\":987,\"pid\":789,\"groupBy\":\"2023-01-22\"}",
    "request_type": "PUT",
    "time_entry_id": "123",
    "workspace_id": "987"
  },
  "payload": {
    "at": "2023-01-22T06:17:23+00:00",
    "billable": false,
    "description": "toggl bridge",
    "duration": 301,
    "duronly": true,
    "id": 123,
    "pid": 789,
    "project_id": 789,
    "server_deleted_at": null,
    "start": "2023-01-22T06:12:22Z",
    "stop": "2023-01-22T06:17:23Z",
    "tag_ids": null,
    "tags": [],
    "task_id": null,
    "uid": 456,
    "user_id": 456,
    "wid": 987,
    "workspace_id": 987
  },
  "subscription_id": 4438
}
zmoog commented 1 year ago

Adding an example for deleting a time entry:

{
  "event_id": 6680092444081150,
  "created_at": "2023-01-22T06:32:18.509Z",
  "creator_id": 123,
  "metadata": {
    "action": "deleted",
    "event_user_id": "123",
    "model": "time_entry",
    "model_owner_id": "123",
    "path": "/api/v9/time_entries/789",
    "request_type": "DELETE",
    "time_entry_id": "789",
    "workspace_id": "456"
  },
  "payload": {
    "at": "2023-01-22T06:31:59+00:00",
    "billable": false,
    "description": "test",
    "duration": 2700,
    "duronly": true,
    "id": 789,
    "project_id": null,
    "server_deleted_at": null,
    "start": "2023-01-21T06:15:00+00:00",
    "stop": "2023-01-21T07:00:00+00:00",
    "tag_ids": null,
    "tags": null,
    "task_id": null,
    "user_id": 123,
    "workspace_id": 456
  },
  "subscription_id": 4438
}