Webhooks

Time Stream can send webhooks to external services when certain events occur.

About Time Stream webhooks

Webhooks are a way to notify external services when certain events occur in Time Stream.

In keeping with the principles of privacy and control, Time Stream does not store or send your data to any external services unless you choose to do so by setting up a webhook endpoint.

When sending webhooks, Time Stream does so right from your browser. Your data is not stored on or sent to our servers in any way; the webhook is sent directly from your browser to the webhook URL you provide.

In some instances our choice to send webhooks directly from your browser can be a limitation, but this implementation truly respects your privacy and data.

Supported events

Time Stream will send webhooks when the following events occur:

Entry events

  • Entry created
  • Entry updated
  • Entry deleted
Entry related events are created when entries are created via the Timer or manually in the Entries view.

Backup events

  • Backup created

You must select at least one event type when configuring a webhook.

Setting up a webhook endpoint

Before Time Stream will send data to an external service, you need to setup a webhook endpoint. This information will be used to send a webhook each time a relevant event occurs in Time Stream.

To set up a webhook endpoint, you need to have a URL that can receive data.

Follow these steps to set up a webhook endpoint:

  • Click on the cog icon in the top right corner of the app.
  • Choose Manage webhooks.
  • Click the Create button.
  • Provide a URL for the webhook and configure the remaining options.
  • Click the Save button.

Webhook endpoint options

When creating a webhook endpoint, you can configure the following options.

Name

You can provide a descriptive name for your webhook to help you identify it. This is optional but recommended for easier management.

URL

The URL where webhook requests will be sent. This is required and should be a valid HTTPS URL.

Webhook body content

This setting allows you to control how the webhook payload is formatted. You can choose the following options:

  • JSON - The webhook content will be sent as a JSON object.
  • Signed JSON (JWS) - The webhook content will be signed with a secret key using JSON Web Signature (JWS).
  • Encrypted JSON (JWE) - The webhook content will be encrypted with a secret key using JSON Web Encryption (JWE).
  • Signed and Encrypted JSON (JWS+JWE) - The webhook content will first be signed using JWS, then encrypted using JWE.

When you create a webhook endpoint, Time Stream automatically generates unique secrets that are used to sign and/or encrypt the webhook payloads based on your selected format.

The secrets can be used to verify and decrypt incoming webhooks:

  • Signing secret - Used to verify the signature of webhooks sent with JWS signing to ensure authenticity.
  • Encryption secret - Used to decrypt webhooks sent with JWE encryption to access the payload.

Time Stream uses the following formats:

  • JWS uses compact serialization with HS256 algorithm (HMAC with SHA-256).
  • JWE uses compact serialization with A256KW key wrapping and A256GCM content encryption.

Include headers

If you enable this option, Time Stream will set the Content-Type header to one of the following:

  • application/json - If you choose JSON for the webhook content.
  • application/jose - If you choose any of the signing or encryption options (JWS/JWE) for the webhook content.

If you do not enable this option, Time Stream will not set the Content-Type header, but it will be defaulted to text/plain by your browser.

Be aware that enabling this option will trigger a CORS preflighted request. Read more about CORS.

Method

The method to use when sending the webhook. The default is POST, but you can also choose PUT if your webhook endpoint requires it.

Be aware that choosing PUT will trigger a CORS preflighted request. Read more about CORS.

Enabled

You can enable or disable the webhook. When disabled, Time Stream will not make any requests to the URL provided.

Event types

When creating a webhook, you must select which event types will trigger the webhook. This allows you to send webhooks to different services for different types of activities.

Event selections are organized into two categories:

  • Entry events - related to time entries (created, updated, deleted)
  • Backup events - related to backup operations (created)
At least one event type must be selected before the webhook endpoint can be created.

Tag filters

If you've selected any entry-related events, you can also filter by tags. This allows you to send webhooks only for entries with specific tags.

If you don't configure a tag filter, all entries will be considered for the selected entry-related event types.

Tag filters only apply to entry-related events and have no effect on backup events.

Tags

The tags to filter by. You can choose one or more tags from your existing tag collection. Only entries that match these tags will trigger webhooks.

Tags filter type

The type of filter to apply to the tags. You can choose between:

  • Any - Will send the webhook if the entry has any of the chosen tags (more inclusive).
  • All - Will send the webhook only if the entry has all of the tags you have chosen (more restrictive).

Webhooks

Webhooks are generated for each event that occurs in Time Stream. Once generated, the webhook is sent to the webhook endpoint URL you provided.

Webhook payload

The webhook payload is the data that will be sent to the webhook URL.

The payload will be a JSON object containing the event data and the related entry (found within the data property).

1{
2 "id": "A7E1331A63BB0D69",
3 "createdAt": "2025-02-24T10:54:39.434Z",
4 "object": "event",
5 "data": {
6 "id": "d74c4f01-b9bd-44b2-b647-bfdaf2396f2d",
7 "completed": true,
8 "ended": "2025-02-11T05:28:59.363Z",
9 "isPomodoro": true,
10 "minutes": 25,
11 "note": "Pomodoro note containing a description of the task.",
12 "started": "2025-02-11T05:02:59.382Z",
13 "tags": [
14 "Client ABC",
15 "Project 123"
16 ],
17 "isManual": false,
18 "tagIds": [
19 "7731e24d-ba6a-43ab-b31b-86ff64f36258"
20 ]
21 },
22 "type": "entry.updated"
23}

Security features

To enhance security, Time Stream offers signing and encryption of webhook payloads.

Webhook content can be formatted in four different ways:

  • JSON - The webhook content will be sent as a JSON object.
  • Signed JSON - The webhook content will be signed with a secret key using JSON Web Signature (JWS).
  • Encrypted JSON - The webhook content will be encrypted with a secret key using JSON Web Encryption (JWE).
  • Signed and Encrypted JSON - The webhook content will first be signed using JWS, then encrypted using JWE.

JSON Web Signature (JWS)

When you choose the "Signed JSON" option, Time Stream signs the JSON payload using JWS with the HMAC-SHA256 algorithm (HS256). This provides data integrity and authenticity verification, allowing recipients to verify that the webhook came from Time Stream and that the data hasn't been tampered with during transmission.

To verify a JWS signature, you'll need the signing secret that was automatically generated when you created the webhook endpoint.

JSON Web Encryption (JWE)

When you choose the "Encrypted JSON" option, Time Stream encrypts the JSON payload using JWE with A256KW (AES Key Wrapping with 256-bit key) for key management and A256GCM (AES-GCM with 256-bit key) for content encryption. This provides confidentiality, ensuring that only the intended recipient with the encryption key can read the webhook data.

To decrypt a JWE payload, you'll need the encryption secret that was automatically generated when you created the webhook endpoint.

Combined JWS + JWE

For maximum security, you can choose the "Signed and Encrypted JSON" option, which first signs the JSON payload with JWS and then encrypts the signed payload with JWE. This provides both authenticity verification and confidentiality.

To process these webhooks, you'll need both the signing secret and the encryption secret. First decrypt the payload using the encryption secret, then verify the signature using the signing secret.

Webhook status

The webhook status is the status of the webhook. It can be one of the following:

  • pending - The webhook is pending.
  • sent - The webhook has been sent.
  • failed - The webhook has failed.

The webhook status is pending when the webhook will be sent or retried at some point in the future.

The webhook status is sent when the webhook has been sent to the webhook endpoint URL and the response had a status in the 200-299 range. If the response had a status outside the 200-299 range, the webhook will be scheduled for a retry.

The webhook status is failed when the webhook has been retried up to 5 times and all attempts have failed.

CORS

Time Stream sends webhooks directly from your browser, and as such, it may trigger CORS preflighted requests.

CORS stands for Cross-Origin Resource Sharing. It is a security feature in browsers that prevents malicious websites from making requests to your webhook endpoint URL.

Depending on the options you choose when creating a webhook endpoint, Time Stream may make a CORS preflighted request rather than a simple request to the webhook URL you provide.

A preflighted request enforces some requirements that need to be met by the platform or server on which the URL that will be receiving the request is hosted.

Simple requests

A simple request is a request made by the browser to the webhook endpoint URL you provide. Only one request is made and it will contain all information about the event related to an entry.

The request will be made with the following characteristics:

  • A Method: POST request.
  • A Content-Type: text/plain header.
  • A JSON body containing the event data (this may or may not include a signature for verification).

Preflighted requests

A preflighted request involves making two requests to the endpoint URL you provide. The first request made by the browser is to check if the webhook endpoint URL you provide is allowed to receive webhooks. If the conditions are met, the second request is made to the webhook endpoint URL you provide.

Choosing any of the following options will trigger a preflighted request:

  • Enabling the Include headers option.
  • Choosing a PUT method.

The first request will be an OPTIONS request with the following characteristics:

  • A Method: OPTIONS header.
  • A Content-Type: application/json header.
  • An Origin: https://my.timestream.app header.
  • An Access-Control-Request-Method: POST header.
  • A Access-Control-Request-Headers: content-type header.

The server will need to respond with:

  • An Access-Control-Allow-Headers: content-type header.
  • An Access-Control-Allow-Methods: POST header.
  • An Access-Control-Allow-Origin: https://my.timestream.app header.
  • A 204 No Content status code.

If everything is accurate the second request will be a POST request with the following characteristics:

  • A Method: POST header.
  • A Content-Type: application/json header for JSON payloads, or application/jose for signed or encrypted payloads.
  • An Origin: https://my.timestream.app header.
  • The payload in the format you selected (JSON, JWS, JWE, or JWS+JWE).

If the server responds with a status in the 200-299 range, the webhook will be considered sent.

If the server responds with a status outside the 200-299 range, the webhook will be scheduled for a retry.