Box User Events: REST API & Python

Rui Barbosa
Box Developer Blog
Published in
4 min readAug 1, 2023

--

Image By vecstock

The Box Platform publishes events that can subscribed by applications. These events relate to any actions performed by either normal or service users.

Events are organized by user events, meaning events triggered under the security context of a single user, or enterprise events which mean events related with any user or content within the context of an enterprise.

In this article we will focus on user events.

You can subscribe different stream types:

  • all — every event for a user
  • changes — content changes
  • sync — changes relative to sync folders

User Events

Provide a low latency stream of events related to the currently authenticated user.

Your application can take advantage of this to notify the user of some action taken by your app that impacted their box account.

A typical get to the /events end point would look like this:

curl --location 'https://api.box.com/2.0/events?stream_type=all' \
--header 'Authorization: Bearer Q0...ZE'

Returning:

{
"entries": [
{
"type": "event",
"event_id": "0a0dbedf101e537a6a1252924a7da9d966d91745",
"created_by": {
"type": "user",
"id": "18622116055",
"name": "Rui Barbosa",
"login": "..."
},
"created_at": "2023-06-20T06:58:26-07:00",
"recorded_at": "2023-06-20T06:58:27-07:00",
"event_type": "ITEM_CREATE",
"session_id": "37s0swhf516mcne1",
"source": {
"type": "folder",
"id": "213534631621",
...
}
}
],
"chunk_size": 1,
"next_stream_position": 30400984754258517
}

A similar event query using the Python SDK:

def main():
"""
Simple script to demonstrate the use of events
"""

client = get_client(conf)

stream_position = 0
events = client.events().get_events(
limit=10,
stream_position=stream_position,
stream_type="all"
)
stream_position = events["next_stream_position"]

for event in events["entries"]:
print(
f"Got {event.event_type} on {event.created_at}: {event.source.type} {event.source.id} {event.source.name}"
)

Has this output:

Got ITEM_UPLOAD on 2023-07-10T05:39:35-07:00: file 1256595598279 IMG_20190406_160049.jpg
Got ITEM_TRASH on 2023-07-10T05:40:27-07:00: file 1204688948039 Get Started with Box.pdf
Got ITEM_PREVIEW on 2023-07-10T05:40:41-07:00: file 1256595598279 IMG_20190406_160049.jpg
Got ITEM_TRASH on 2023-07-10T05:40:54-07:00: file 1256595598279 IMG_20190406_160049.jpg
Got ITEM_UNDELETE_VIA_TRASH on 2023-07-10T05:41:02-07:00: file 1204688948039 Get Started with Box.pdf

Notice how the call returns a next_stream_position , that you can send back as stream_position so you can continue where you left off.

Long Polling

User events also support long polling, meaning you execute a get to the API for which the connection remains open. Once an event is triggered, you get a response back.

Note that you're not opening a web socket connection.

This is a two step process.

First you send an OPTIONS request to the /events end point, and it returns the URL needed for the long polling.

When you send a GET to the returned URL, the connections will stay open until a an event is sent. For example:

curl --location --request OPTIONS 'https://api.box.com/2.0/events' \
--header 'Authorization: Bearer D8...sF'

Resulting in:

{
"chunk_size": 1,
"entries": [
{
"type": "realtime_server",
"url": "https://2.realtime.services.box.net/subscribe?channel=b188ef734f6f71fa6c85&stream_type=all",
"ttl": "10",
"max_retries": "10",
"retry_timeout": 610
}
]
}

Then we GET the returned URL:

curl --location 'https://2.realtime.services.box.net/subscribe?channel=b188ef734f6f71fa6c85&stream_type=all' \
--header 'Authorization: D8...sF'

And it get this result after and event is triggered:

{
"message": "new_change",
"source": "uesi"
}

As you can see there is no information on the response. It is just informing the client that something new happened. Now the client should query the /events end point with the last known stream_position to get the new events.

curl --location 'https://api.box.com/2.0/events?stream_type=all&stream_position=30400986485844810&limit=50' \
--header 'Authorization: Bearer D8...sF'
{
"entries": [
{
"type": "event",
"event_id": "4f68ef94037862ddaadb74ff33e8d2758cf9a131",
...
"source": {
"type": "file",
"id": "1256652018464",
}
},
{
"type": "event",
"event_id": "5fb318e46a7002104cb7db2223729b1b724ed294",
...
"source": {
"type": "file",
"id": "1256624574539",
}
}
],
"chunk_size": 2,
"next_stream_position": 30400986485908488
}

In the above example I've trashed two files.

Now the client should restart the process by sending an OPTIONS to the /events end point to get a new URL and send a GET that URL, waiting until a new event is received.

{
"chunk_size": 1,
"entries": [
{
"type": "realtime_server",
"url": "https://2.realtime.services.box.net/subscribe?channel=b188ef734f6f71fa6c85&stream_type=all",
"ttl": "10",
"max_retries": "10",
"retry_timeout": 610
}
]
}

Notice that the OPTIONS response includes a ttl , a max_retries , and a retry_timeout . If any of these occur, your app needs to start with the OPTIONS again.

Long Polling using the SDK's

This all becomes much simpler if we use one of the Box SDK's.

Consider this python script:

def main():
"""
Simple script to demonstrate the use of events
"""

client = get_client(conf)

print("Long polling for events...")
events = client.events().generate_events_with_long_polling()
for event in events:
print(
"\t",
event.created_at,
event.event_type,
event.source.type,
event.source.id,
event.source.name,
)

Deleting some files in the app, results in:

Long polling for events...
2023-07-10T08:16:39-07:00 ITEM_TRASH file 1256624574539 IMG_20190406_160049.jpg
2023-07-10T08:16:38-07:00 ITEM_TRASH file 1256652018464 box-dev-logo (1).png

Notice that sending a new OPTIONS , and managing the last stream position is all taken care by the SDK. Essentially the generate_events_with_long_ polling method, returns a generator, that keeps polling the next sequence of events, every time it receives the new_change message.

Further considerations

Box does not store events indefinitely.

User events are stored for between two weeks and two months, after which the user events are removed.

The emphasis of this feed is to return the complete results quickly, which means that Box may return events more than once or out of order. Duplicate events can be identified by their event IDs.

It is not possible to filter User Events on the API request, although it is supported for the Enterprise Events.

If you liked this article please give us a clap.

Feel free to reach out to us via comments on this article or drop us a line at our forum.box.com

The examples above can be easily replicated using this GitHub repo.

If you want to get started using the Box Platform, check out our template python app on this GitHub repo.

--

--