Terra Docs
Dashboard
  • Docs
  • API Reference
  • Changelog
  • Getting Started
    • What is Terra API?
    • Account setup and API keys
    • Core concepts
  • Health & Fitness API
    • Overview
    • Quickstart
    • Integration setup
      • Understanding sources and destinations
      • Setting up data sources
      • Setting up data destinations
        • Webhooks
        • SQL database (Postgres, MySQL)
        • Supabase
        • Cloud storage (S3, GCP)
        • Queuing services (SQS, Kafka)
      • Customising data types
      • Dedicated data source API keys
      • Understanding Terra environments
    • User authentication
      • Authentication flow
      • Implementation (Terra widget)
      • Implementation (Custom UI)
      • Handling authentication events
      • Customising authentication redirects
    • Managing user health data
      • Receiving health data updates (events)
      • Requesting historical health data (REST API requests)
      • Writing data
    • Mobile-only sources
      • iOS (Swift)
      • Android (Kotlin)
      • React Native
      • Flutter
    • Troubleshooting
    • Pricing
  • User Engagement
    • Health Scores
  • Streaming API
    • Overview
    • Wearable -> Your app
      • iOS (Swift)
      • Android
    • Your app -> Terra
      • iOS (Swift)
      • Android
    • Terra -> Your backend
  • Teams API
  • Biomarkers API - Upcoming
Powered by GitBook
On this page
  • Webhook
  • Security
  • Payload signing
  • IP Whitelisting
  • Retries

Was this helpful?

  1. Health & Fitness API
  2. Integration setup
  3. Setting up data destinations

Webhooks

Webhook

Webhooks are the most basic Destination to set up, and involve Terra making a POST request to a predefined callback URL you pass in when setting up the Webhook.

They are automated messages sent from apps when something happens.

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 endpoint URL where you can process it however you see fit.

After a user authenticates with your service through Terra, you will automatically begin receiving webhook messages containing data from their wearable..

Security

Exposing a URL on your server can pose a number of security risks, allowing a potential attacker to

  • Launch denial of service (DoS) attacks to overload your server.

  • Tamper with data by sending malicious payloads.

  • Replay legitimate requests to cause duplicated actions.

among other exploits.

In order to secure your URL, Terra offers two separate methods of securing your URL endpoint

Payload signing

Every webhook sent by Terra will include HMAC-based signature header terra-signature , which will take the form:

t=1723808700,v1=a5ee9dba96b4f65aeff6c841aa50121b1f73ec7990d28d53b201523776d4eb00

In order to verify the payload, you may use one of Terra's SDKs as follows:

Terra requires the raw, unaltered body of the request to perform signature verification. If you’re using a framework, make sure it doesn’t manipulate the raw body (many frameworks do by default)

Any manipulation to the raw body of the request causes the verification to fail.

from terra.base_client import Terra

terra = Terra(api_key='YOUR API KEY', dev_id='YOUR DEV ID', secret='YOUR TERRA SECRET')

# Code to receive a webhook using Django
@csrf_exempt
def my_webhook_view(request):
    payload = request.body
    sig_header = request.META['TERRA_SIGNATURE']
    if not check_terra_signature(payload, sig_header):
        # Invalid signature
        print("Error verifying webhook signature")
        return HttpResponse(status=400)
const { default: Terra } = require("terra-api");

const terra = new Terra(devId: string, apiKey: string, secret: string);

async function handleWebhook(req) {
  // webhook handler
  console.log(req);
}

// using the Express framework
// expose POST /hook endpoint on the server
// this is where Terra will send webhooks
app.post('/hook', (req, res) => {
  // verify the signature of the payload
  if (!terra.checkTerraSignature(req.headers['terra-signature'], req.rawBody)) res.sendStatus(401);
  res.sendStatus(200);
  handleWebhook(req);
});
import co.tryterra.terraclient.TerraClientFactory;
import co.tryterra.terraclient.api.TerraApiResponse;
import co.tryterra.terraclient.api.TerraClientV2;
import co.tryterra.terraclient.api.User;
import co.tryterra.terraclient.exceptions.TerraRuntimeException;
import co.tryterra.terraclient.models.Athlete;
import co.tryterra.terraclient.WebhookHandlerUtility
```

// Using the Spark framework (http://sparkjava.com)
public Object handle(Request request, Response response) {
  String payload = request.body();
  String sigHeader = request.headers("terra-signature");
  
  
  // Find your secret on https://dashboard.tryterra.co/dashboard/connections
  WebhookHandlerUtility handlerUtility = WebhookHandlerUtility("SIGNING_SECRET");
  
  Bool validSignature = handlerUtility.verifySignature(sigHeader, payload);
  
  if (!validSignature) {
    // the signature is invalid
    response.status(401);
    return "";
  }
  
  // Deserialize the object inside the event & handle the event
  // ...
  response.status(200);
  return "";
}

We recommend that you use our official libraries to verify webhook event signatures. You can however create a custom solution by following this section.

The terra-signature header included in each signed event contains a timestamp and one or more signatures that you must verify.

  • the timestamp is prefixed by t=

  • each signature is prefixed by a scheme. Schemes start with v, followed by an integer. (e.g. v1)

terra-signature:
t=1492774577,
v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd,
v0=6ffbb59b2300aae63f272406069a9788598b792a944a07aba816edb039989a39

To create a manual solution for verifying signatures, you must complete the following steps:

Step 1: Extract the timestamp and signatures from the header

Split the header using the , character as the separator to get a list of elements. Then split each element using the = character as the separator to get a prefix and value pair.

The value for the prefix t corresponds to the timestamp, and v1 corresponds to the signature (or signatures). You can discard all other elements.

Step 2: Prepare the signed_payloadstring

The signed_payload string is created by concatenating:

  • The timestamp (as a string)

  • The character .

  • The actual JSON payload (that is, the request body)

Compute an HMAC with the SHA256 hash function. Use the endpoint’s signing secret as the key, and use the signed_payload string as the message.

Compare the signature (or signatures) in the header to the expected signature. For an equality match, compute the difference between the current timestamp and the received timestamp, then decide if the difference is within your tolerance.

To protect against timing attacks, use a constant-time-string comparison to compare the expected signature to each of the received signatures.

IP Whitelisting

IP Whitelisting allows you to only allow requests from a preset list of allowed IPs. An attacker trying to reach your URL from an IP outside this list will have their request rejected.

The IPs from which Terra may send a Webhook are:

  • 18.133.218.210

  • 18.169.82.189

  • 18.132.162.19

  • 18.130.218.186

  • 13.43.183.154

  • 3.11.208.36

  • 35.214.201.105

  • 35.214.230.71

  • 35.214.252.53

Retries

If your server fails to respond with a 2XX code (either due to timing out, or responding with a 3XX, 4XX or 5XX HTTP code), requests to it will be retried with exponential backoff around 8 times over the course of just over a day.

PreviousSetting up data destinationsNextSQL database (Postgres, MySQL)

Last updated 1 month ago

Was this helpful?

Terra generates signatures using a hash-based message authentication code () with . To prevent , ignore all schemes that aren’t v1

Step 3: Determine the expected signature

Step 4: Compare the signatures

HMAC
SHA-256
downgrade attacks