Webhooks
Webhooks allow you to receive real-time HTTP notifications whenever certain events occur on rememberthemilk.com. For example, we could send you a notification when any of your app users create a new task or whenever they complete a task. This prevents you from having to query the Remember The Milk API for changes that may or may not have happened and helps you avoid reaching your rate limit.
How it works
- You make an API call to Remember The Milk asking to subscribe to the topics you want, providing a callback URL in the arguments.
- To verify the subscription, we make a request to your callback URL. If you respond appropriately, we're all good and from then on…
- Real-time updates are POSTed from Remember The Milk to your callback URL.
Endpoint
Your endpoint must be able to process two types of HTTPS requests: Verification Requests and Event Notifications. Since both requests use HTTPS, your server must have a valid TLS or SSL certificate correctly configured and installed. Self-signed certificates are not supported.
Subscribing
Before creating a subscription, you may want to check the list of available topics by calling rtm.push.getTopics
. This method tells you what you can subscribe to. It returns something like this:
{ "rsp": { "stat": "ok", "topics": { "topic": [ "changes_available", "task_completed", "task_created", "task_received", "task_tagged" ] } } }
Use rtm.push.subscribe
to create a subscription. It takes the following arguments:
- url - The URL of your endpoint. It must be unique, i.e., you can't use the same URL for more than one subscription.
-
topics - A comma-delimited list of topics returned by
rtm.push.getTopics
. - filter - Allows you to filter tasks by the provided criteria. Not all of the topics support filtering. See the list of available topics for more details.
-
push_format - A format of the request (
xml
orjson
). - lease_seconds - An optional number of seconds after which the subscription will be deleted. The supported range is from 1 minute to 1 day. If not specified, the subscription will auto-renew itself indefinitely.
This method returns a newly created subscription in a pending state, like so:
{ "rsp": { "stat": "ok", "transaction": { "id": "61021", "undoable": "0" }, "subscription": { "id": "135", "url": "https://hooks.example/", "topics": { "topic": [ "task_created", "task_completed" ] }, "filter": "list:inbox", "format": "json", "expires": "2049-07-06T00:00:00Z", "pending": "1" } } }
We send a verification request to the provided URL and, if the request is successful, activate the subscription.
Verification Request
The events sent to your callback URL may contain sensitive information associated with the users having approved your app. To ensure that events are being delivered to a server under your direct control, we must verify your ownership by issuing you a challenge request.
Anytime you create a subscription, we'll send a POST request to your endpoint URL with an X-Hook-Secret
header that has a unique string as its value. Whenever your endpoint receives a verification request, it must return a 200 OK HTTP response with the secret included in the X-Hook-Secret
header. If the secrets match, the subscription is activated.
Event Notifications
When an event in your subscription occurs in an authorized user's account, we'll send an HTTP POST request to your callback URL. The URL will receive a request for each event matching your subscriptions. One request, one event:
{ "id": "c6ac89b9-ad76-3d19-9043-64b35e9b1259", "ts": "2049-07-06T19:55:45Z", "type": "task_created", "data": { "list": { "id": "677", "name": "Inbox", "taskseries": [ { "id": "179631632", "created": "2049-07-06T19:55:45Z", "location_id": "49", "modified": "2049-07-06T19:55:45Z", "name": "a task", "notes": { "note": [ { "id": "33601926", "created": "2049-07-06T19:55:45Z", "modified": "2049-07-06T19:55:45Z", "title": "", "content": "some notes" } ] }, "parent_task_id": "", "source": "js", "participants": [], "tags": {"tag": ["baz", "foo", "bar"]}, "task": [ { "id": "287044175", "added": "2049-07-06T19:55:45Z", "completed": "", "deleted": "", "due": "2049-07-07T19:55:45Z", "has_due_time": "0", "start": "2049-07-06T19:55:45Z", "has_start_time": "0", "estimate": "PT30M", "postponed": "0", "priority": "1" } ], "url": "", "rrule": { "rule": "FREQ=DAILY;INTERVAL=1;WKST=SU", "every": "1" }, "assignee": { "username": "hooks.tester", "email": "hooks.tester@example.com", "status": "accepted" } } ] } } }Your endpoint should respond to all Event Notifications with 200 OK HTTP. If it does not, we'll consider the event delivery attempt failed. After a failure, we'll retry ten times, backing off exponentially.
Validating events
We sign all Event Notifications with a HMAC-SHA256 signature and include the signature in the request's X-Hook-Signature
header, preceded with the current time in Unix epoch seconds. You don't have to validate the payload, but you should. Let's presume that our shared secret is BANANAS and you receive the following event:
POST / HTTPS/1.1 Content-Type: application/json X-Hook-Signature: 2509214145.d480e0d30206a376441d6aa555533eae4283ee1afc1753891abc141c2bf1d7fc { "id": "c6ac89b9-ad76-3d19-9043-64b35e9b1259", "ts": "2049-07-06T19:55:45Z", "type": "task_created", "data": ... }
To validate the event:
-
Split
X-Hook-Signature
value on the dot:timestamp = 2509214145
signature = d480e0d30206a376441d6aa555533eae4283ee1afc1753891abc141c2bf1d7fc
-
Construct a string with the timestamp and the event id concatenated together:
2509214145.c6ac89b9-ad76-3d19-9043-64b35e9b1259
-
Generate a HMAC-SHA256 signature using the resulting string and our shared secret BANANAS:
>>> hash_hmac('sha256', '2509214145.c6ac89b9-ad76-3d19-9043-64b35e9b1259', 'BANANAS') d480e0d30206a376441d6aa555533eae4283ee1afc1753891abc141c2bf1d7fc
-
Compare your signature to the signature in the
X-Hook-Signature
header (everything after the timestamp). If the signatures match, the payload is genuine.
List active subscriptions
Use rtm.push.getSubscriptions
method to retrieve the list of active subscriptions. Here's what an example response would look like:
{ "rsp": { "stat": "ok", "subscriptions": { "subscription": [ { "id": "135", "url": "https://hooks.example", "topics": {"topic": ["task_created"]}, "filter": "list:inbox", "format": "json", "expires": "", "pending": "0" }, { "id": "136", "url": "https://hooks.another.example", "topics": {"topic": ["task_created", "task_completed"]}, "filter": "due:today", "format": "xml", "expires": "2049-07-06T00:00:00Z", "pending": "0" } ] } } }
Unsubscribing
You can unsubscribe from event notifications in one of the following ways:
-
If you supply lease_seconds parameter to
rtm.push.subscribe
method, the subscription is canceled automatically after the lease expires. - If your endpoint returns 410 Gone HTTP, the associated subscription is canceled immediately.
-
You can call
rtm.push.unsubscribe
with the subscription_id parameter to cancel a subscription with the specified id.
Order of events
Remember The Milk does not guarantee delivery of events in the order in which they are generated. For example, creating a task produces the following events:
changes_available
task_created
Handle duplicate events
Webhook endpoints might occasionally receive the same event more than once. We advise you to guard against duplicated event receipts by making your event processing idempotent. One way of doing this is logging the events you've processed, and then not processing already-logged events.
Supported topics
Topic | Description | Filterable |
changes_available |
Triggers when any changes are available. | No |
task_completed |
Triggers when a task is completed. | Yes |
task_created |
Triggers when a task is created. | Yes |
task_received |
Triggers when a user is given a task (before the task is accepted or rejected). | No |
task_tagged |
Triggers when task tags are updated. | Yes |