API

Build cool stuff that works with Remember The Milk.

menu

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

  1. You make an API call to Remember The Milk asking to subscribe to the topics you want, providing a callback URL in the arguments.
  2. To verify the subscription, we make a request to your callback URL. If you respond appropriately, we're all good and from then on…
  3. 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 or json).
  • 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:

  1. Split X-Hook-Signature value on the dot:
    timestamp = 2509214145
    signature = d480e0d30206a376441d6aa555533eae4283ee1afc1753891abc141c2bf1d7fc
  2. Construct a string with the timestamp and the event id concatenated together:
    2509214145.c6ac89b9-ad76-3d19-9043-64b35e9b1259
  3. 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
                
  4. 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
Your endpoint should not expect delivery of these events in this order and should handle this accordingly.

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

API methods