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 |
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 |
retry_after_seconds? | Double | Time to wait before making the same request again. Only sent when |
widget_session_id? | UUID | ID of the widget session that this event relates to. Only sent where |
reference_id? | String | Reference ID passed in to |
reason? | String | Reason for auth failure. Only sent where |
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
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
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
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
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
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
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
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
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
}