What are Webhooks?

Webhooks are automated messages sent from apps when something happens. They have a message—or payload—and are sent to a unique URL—essentially the app's phone number or address. Webhooks are almost always faster than polling, and require less work on your end.

Terra uses webhooks to notify you whenever new data is made available for any of your users. New data, such as activity, sleep, etc will be normalised and sent to your webhook URL at which point you can process it however you see fit.

When making HTTP requests for historical data, by default the request will be sent into our queue for asynchronous processing, and the results will be forwarded to your webhook URL once processing has been completed. Note that you can also receive data in the HTTP response by passing the query parameter to_webhook=false however this is liable to timeout for larger queries.

Getting Started with Webhooks

Creating a webhook endpoint follows exactly the same process as creating any other page on your website. It is an HTTPS secured endpoint on your server with a distinct URL. For testing, you may use HTTP but for production please make sure that your endpoint is correctly secured.

The endpoint you create must be configured to accept POST requests. Note that webhook requests will time out after 10 seconds, so you should ensure that you finish processing the data before the end of the time limit, or you should process the request asynchronously and return a response immediately.

You can configure your webhook URL at any time through the settings page of the developer dashboard.

Retries

Terra implements retry logic to prevent you from missing payloads when your server may have inadvertently returned an error response. Retries are processed on an exponential backoff schedule and will expire after approximately a day if none of the retried requests were successful.

Requests will be retried if you server returns a non 2xx response code, or if the request times out (takes longer than 30 seconds).

Note that all retries are completely encapsulated so changes to settings through the Terra Dashboard will not be applied to the retry attempts.

Basic Example

import flask
from flask import request

app = flask.Flask(__name__)


@app.route("/consumeTerraWebhook", methods=["POST"])
def consume_webhook():
  print("Received webhook")
  # optionally verify request signature
  body = request.get_json()
    # process request body
  return flask.Response(status=200)


if __name__ == "__main__":
    app.run(host="127.0.0.1", port=8080)
const express = require("express");
const app = express();

app.use(express.json());

app.post("/consumeTerraWebhook", (req, res) => {
  console.log("Received webhook");
  let body = req.body;
  // process request body
  res.sendStatus(200);
});

Payload Format

The below table shows all current possible fields that can be returned in the top-level JSON object for a webhook payload. Note that more fields may be added at any time so your implementation should be flexible to allow for this.

Field Name

Type

Description

status

String

The status of the event that the payload is for. Will be one of error, success, warning.

type

String

The type of event that the payload is for.

user

TerraUser

User that the payload information relates to.

message?

String

Human-readable further details relating to the payload.

data?

Array

Data for the webhook request. Only sent where type is a data type: athlete, activity, etc.

retry_after_seconds?

Double

Time to wait before making the same request again. Only sent when type is processing

widget_session_id?

UUID

ID of the widget session that this event relates to. Only sent where type is auth or auth_failure

reference_id?

String

Reference ID passed in to /authenticateUser or when creating the widget session. Only sent where type is auth or auth_failure.

reason?

String

Reason for auth failure. Only sent where type is auth_failure.

List of Event Types

athlete

activity

body

daily

sleep

nutrition

menstruation

auth

user_reauth

connection_error

request_processing

google_no_datasource

request_completed

access_revoked

deauth

Event Type Explanations

Authentication

auth

Occurs upon successful or failed user authentication, and will take the following formats

{
  "type": "auth",
  "user": TerraUser,
  "status": "success",
  "reference_id": String,
  "widget_session_id": String
}
{
  "type": "auth",
  "user": TerraUser,
  "status": "error",
    "message": "User failed to authenticate and has been deleted"
  "reason": String,
  "reference_id": String,
  "widget_session_id": String
}

If the authentication fails, the reason in the payload can take a value between one of either auth_cancelled, meaning that the user cancelled the auth flow at some point in the process, or missing_scopes, meaning that the user didn't grant the necessary scopes to Terra.

user_reauth

Occurs when a user (defined here as one account on a given data provider's end) authenticates for a second time under a single dev ID. Terra's backend will proceed by deleting the old entry for that user, and send you both an auth_success payload as well as a user_reauth one, in either order.

{
  "type": "user_reauth",
  "new_user": TerraUser,
  "old_user": TerraUser,
  "status": "warning",
    "message": "User has reauthenticated and old ID has been deleted"
}

When this is done, you are expected to replace all occurences of the old user_id with the new one, provided in the user_reauth payload

access_revoked

Occurs when a user revokes access to Terra from the data provider's end, and said data provider sends Terra a notification of this access revocation.

{
  "type": "access_revoked",
  "user": TerraUser,
  "status": "warning",
    "message": "User revoked access"
}

Terra then deletes the user record (since the connection has been broken). You should implement logic to delete the record of that user from your end as well

deauth

Occurs when a user successfully deauthenticates through the terra API, such as for example when pressing a "remove connection" button within your UI, or when doing so through the widget.

{
  "type": "deauth",
  "user": TerraUser,
  "status": "success",
    "message": "User has deauthenticated"
}

This signifies that it is safe to delete that user record from your end.

google_no_datasource

Occurs when a user authenticates through Google Fit, but has no data sources available for their account (all subsequent data requests will return empty data unless this changes)

{
    "type": "google_no_datasource",
  "status": "warning",
  "message": "User has no datasources available",
  "user": TerraUser
}

connection_error

Occurs when any request to a provider returns an HTTP response code among 401 - Unauthorized, 403 - Forbidden, or 412 - Precondition Failed.

{
    "type": "connection_error",
  "status": "warning",
  "message": "User connection degraded",
  "user": TerraUser
}

This usually occurs if a user has revoked access to Terra from the data provider's end, but the provider does not inform Terra of this access revocation.
You are advised to hit the terra Deauthenticate User endpoint for that user, or ask them to reauthenticate, as that connection will no longer be able to yield any data

Data requests

request_processing

Occurs when a large request (>1 month of data) has been submitted to the rest API, and week-long segments of data will be sent following this webhook
The reference field will be included as a terra-reference header in every data payload following this hook, and in the request_completed hook shown below

{
    "type": "google_no_datasource",
  "status": "processing",
  "message": "Large request is processing",
  "user": TerraUser,
  "reference": String
}

request_completed

Occurs when all the data transfer for a large request has been completed

{
  "type": "request_completed",
  "status": "completed",
  "message": "Large request has been completed",
  "user": TerraUser,
  "reference": String
}