Webhook
[Webhooks] allow API consumers to receive notifications about events involving their users (e.g. when a workout has been saved). These notifications may be sent to any secure URL maintained by the consumer, and they are very lightweight—typically no more than some identifying information for the event and a URL to get more information.
Consumers subscribe to these notifications via a simple POST
request with some
basic information about what kind of event is of interest, the URL to which
notifications should be sent, and a secret value (so you can be confident the
event came from us).
In order to meet our own performance requirements, we require Webhook subscribers to respond quickly when notified. We expect each notification to be responded to within 3 seconds. See our Tips section below for some advice on how to meet this latency requirement.
Resource URIs
- Item URI:
/v7.0/webhook/{id}/
- Collection URI:
/v7.0/webhook/
How to use Webhooks
Since the notifications we send are lightweight, API consumers should get the
latest information available by issuing a GET
to the URL included in the
notification. The diagram below illustrates the process:
In this example, the subscriber is notified some time after the user has saved a workout. (Generally this is done very quickly.) The notification includes three main pieces of information:
- the notification type (e.g.
application.workouts
) - a URI on which a client can request the latest resource data
- enough information about the user for you to determine which access token to use when you make subsequent requests
The subscriber must return a response with status code
202 Accepted
. This should emphasize that not much work is being done with the notification at the moment of receipt, as subscribers should respond within 3 seconds of receiving a message. This response code and timing is checked on creation and if a timely202
isn’t received, the webhook creation will fail.
Webhook payload
Below are some example payloads we might send, by subscription type:
application.workouts
[{
"type": "application.workouts",
"ts": "2014-05-15T01:51:35.796829+00:00",
"object_id": "1",
"_links": {
"workout": [{
"href": "\\/v7.0\\/workout\\/1\\/",
"id": "1"
}],
"user": [{
"href": "\\/v7.0\\/user\\/1\\/",
"id": "1"
}]
}
}]
As with other responses in the MapMyFitness API, we provide an href
attribute inside
the _links
object. This is the URI to call for the latest data on this object.
Note: While you certainly can construct the object’s URI (e.g.
/v7.0/workout/1/
), we recommend that you use the href
value instead.
This provides forward compatibility into later (backwards compatible) versions
of the API, changes we might implement such as caching parameters, etc. In other
words, it helps make your application a good citizen.
Request Headers
Each notification will have 2 headers:
content-type
The value will beapplication/json
HMAC-Signature
The hash signature calculated from your webhook’sshared_secret
and the notification’s payload. We use HMAC/SHA-1. You can use this signature to validate the message’s integrity by calculating it on your side and comparing the received value. This validation process is optional.
Python Code example on how to calculate the signature for a given notification payload
import hmac
import hashlib
import json
webhook_shared_secret = 'this_is_a_secret'
notification_data = [{
"type": "application.workouts",
"ts": "2014-05-15T01:51:35.796829+00:00",
"object_id": "1",
"_links": {
"workout": [{
"href": "\\/v7.1\\/workout\\/1\\/",
"id": "1"
}],
"user": [{
"href": "\\/v7.1\\/user\\/1\\/",
"id": "1"
}]
}
}]
notifications_json_str = json.dumps(notification_data)
signature = hmac.new(webhook_shared_secret, notifications_json_str, digestmod=hashlib.sha1).hexdigest()
print signature # Calculated hash for this payload is b95fbe0fb0e4b9f2cdb88ffbfc4ddcce0331f9f7
Tips
Responding quickly to a notification
In order to respond as quickly to a notification, subscribers should defer any processing of the notification until after the response is done. You may do this in a few ways, but two popular options are to spawn a new thread to handle the processing, or to place the notification in a queue for later processing. Since you’re likely already using queues for your processing, this is probably your best option.
Under no circumstances should you call the included URL before responding.
Dynamically handling the notification payload
Notification payloads are sent as JSON arrays. This is deliberately done for forward compatibility, as we hope to support batch notifications in the future. As a result, you should loop over the payload and treat each element of it as an individual notification.
Additionally, the payload’s content is determined by the notification type. We
recommend you maintain a mapping from the notification’s type
to the content.
E.g., application.workouts => workout
.
Item
Item Methods
GET
Retrieve a Webhook by IDPUT
Update a Webhooks entity
Item properties
Name | Description | Type | HTTP Support |
---|---|---|---|
id |
Webhook ID | number | GET: required, PUT: required |
subscription_type |
The type of subscription | string | Required |
callback_url |
The HTTPS URL that will consume notifications generated by the Webhook. | string | Required |
shared_secret |
A shared secret used to sign outgoing messages | text | GET: required, PUT: required |
status |
Tells whether active or not | text | GET: required, PUT: required |
created |
When this Webhook was created | DateTime | GET: required, PUT: required |
last_updated |
When this Webhook was last updated. | DateTime | GET: required, PUT: required |
last_degraded |
When the Webhook was last degraded. | DateTime | GET: required, PUT: required |
client_id |
client ID associated with this Webhook | number | GET: required, PUT: required |
Example Values
subscription_type
Name | Description |
---|---|
application.workouts |
Notifies of changes in workouts |
Collection
Collection methods
GET
Get a list of Webhooks. An optionalstatus
can be provided to filter the results by their status. Allowed values are:all
,active
,degraded
,disabled
. If nostatus
parameter is given, the list of all Webhook resources with a status different thandisabled
is returned.POST
Create a Webhooks resource. If the resource already exists, a 409 will be returned.
Collection properties
Name | Description | Type | HTTP Support |
---|---|---|---|
total_count |
The total number of Webhooks matching the search parameters specified | int | GET: required |
Collection links
self
A link to this resourceuser
A link to the User resource that owns the Webhook
Embedded collections
Webhooks
A collection of Webhooks with properties as described under Item properties.
Usage
GET Webhook entity
To fetch a single Webhook, make a GET
request to /v7.0/webhook/<id>/
where <id>
is the id of the Webhook you want to fetch.
Request GET /v7.0/webhook/<id>/
Response
{
"status": "active",
"last_updated": "2014-05-08T21:29:14+00:00",
"subscription_type": "application.workouts",
"created": "2014-05-08T21:27:49+00:00",
"last_degraded": "2014-05-08T21:29:04+00:00",
"client_id": "a_valid_client_id",
"shared_secret": "this_is_a_shared_secret",
"_links": {
"self": [
{
"href": "/v7.0/webhook/19/",
"id": "19"
}
],
"documentation": [
{
"href": "https://developer.mapmyfitness.com/docs/${doc_uri}"
}
]
},
"callback_url": "https://example.com/callback",
"id": 19
}
GET Webhooks collection
To fetch all Webhooks associated with your OAuth client, make a GET
request to /v7.0/webhook/
.
An optional status
querystring parameter can be provided. Accepted values are: all
, active
, disabled
, degraded
.
Example: /v7.0/webhook/?status=all
will return all the Webhook resources, no matter what their status is (even disabled ones).
Request GET /v7.0/webhook/
Response
{
"_embedded": {
"webhooks": [
{
"status": "active",
"last_updated": "2014-05-08T21:29:14+00:00",
"subscription_type": "application.workouts",
"created": "2014-05-08T21:27:49+00:00",
"last_degraded": "2014-05-08T21:29:04+00:00",
"client_id": "a_valid_client_id",
"shared_secret": "this_is_a_shared_secret",
"callback_url": "https://example.com/callback",
"id": 19
}
]
},
"_links": {
"self": [
{
"href": "/v7.0/webhook/?limit=20&offset=0"
}
],
"documentation": [
{
"href": "https://developer.mapmyfitness.com/docs/${doc_uri}"
}
]
},
"total_count": 1
}
POST Webhooks entity
To create a Webhook, make a POST
request to /v7.0/webhook/
.
Note: If an identical webhook subscription already exists (for the same callback url and subscription type), the response code will be 409 Conflict. This means notifications should already be flowing.
Request POST /v7.0/webhook/
{
"callback_url": "https://example.com/callback",
"shared_secret": "this_is_a_shared_secret",
"subscription_type": "application.workouts"
}
Response
{
"status": "disabled",
"last_updated": "2014-05-14T16:39:33.151526+00:00",
"subscription_type": "application.workouts",
"created": "2014-05-14T16:39:33.151069+00:00",
"last_degraded": null,
"client_id": "a_valid_client_id",
"shared_secret": "this_is_a_shared_secret",
"_links": {
"self": [
{
"href": "/v7.0/webhook/20/",
"id": "20"
}
],
"documentation": [
{
"href": "https://developer.mapmyfitness.com/docs/${doc_uri}"
}
]
},
"callback_url": "https://example.com/second_callback",
"id": 20
}
PUT Webhooks entity
Altering existing Webhooks can be done through a PUT
request to the item URI.
Currently only the status
is editable – for other changes disable the
existing Webhook and create a new one with the desired properties. The options
for status
are active
, disabled
, degraded
.
Request PUT /v7.0/webhook/<id>/
{
"status": "disabled"
}
Response
{
"status": "disabled",
"last_updated": "2014-05-14T18:28:43+00:00",
"subscription_type": "application.workouts",
"created": "2014-05-08T21:27:49+00:00",
"last_degraded": "2014-05-08T21:29:04+00:00",
"client_id": "a_valid_client_id",
"shared_secret": "this_is_a_shared_secret",
"_links": {
"self": [
{
"href": "/v7.0/webhook/19/",
"id": "19"
}
],
"documentation": [
{
"href": "https://developer.mapmyfitness.com/docs/${doc_uri}"
}
]
},
"callback_url": "https://example.com/callback",
"id": 19
}