Action Log API: how clients use profile events in their own workflows


Alex Phillips
Customer Service Specialist
The standard Octo Browser interface shows you what is happening with your profile right now. But when a team manages hundreds or thousands of profiles, at some point they need to automatically send profile events to a CRM, access control system, or internal dashboard.
That’s what Octo’s Action Log is for: a WebSocket endpoint that sends events in real time. We added it for teams that have integrated Octo into their own CRM, ERP, or accounting system. At the moment, Octo Action Log supports ten event types—from profile launches and stops to transfers, deletions, and proxy changes—and the list is growing as teams discover new use cases.
Below you can find two examples of how you can use Action Log in practice: real-time access control and managing accounts at scale.
Contents
Manage any number of accounts without bans, routine, and unnecessary expenses.
Would you like to try Octo Browser at discount?
Use the promo code OCTOBLOG to get 30% off any subscription. This offer is valid only for new users.
Access rights without manual verification
One of our clients has employees from different departments working with Octo Browser profiles. Each employee has their own access level through a custom extension used with Octo. Before granting the required permissions, the system needs to know who is currently working with a specific profile.
Without Action Log, there was no way to see exactly who was launching a profile. The access-level system had to rely on indirect indicators: it could detect that a profile was being used, but not who was using it.
The process changed when we added the profiles.started: user_email event to Action Log, so permissions could be applied automatically. This event → permissions connection became the foundation for most of our clients’ internal automations.
It’s a continuously running mechanism that responds to every profile launch in real time. Events are processed on the fly and stored in the clients’ own databases.
Our client needed one developer and about an hour to integrate the Action Log. Action Log was added to their existing permissions system as an additional module rather than requiring the process to be rebuilt from scratch.
Team accounting in seconds instead of hours
Another client works with a large volume of accounts using a distributed team: many people and many profiles being processed simultaneously. At this scale, it’s easy to lose sight of the overall picture: how many profiles each person is handling, how many are ready, how many have been assigned, how many have been deleted, and by whom and why.
Before switching to their own system, the team tracked everything in spreadsheets. Any snapshot of the team required manually going through tags and records. Collecting up-to-date data took anywhere from a couple of hours to a full day, and by the time the report was ready, some of the information was already outdated.
After we expanded Action Log, the client built their own CRM that receives profile events via the API and brings them together into a single overview: how many profiles each employee is working on, how many are ready, how many have been assigned, and how many have been deleted, with each action linked to the person who performed it and when.
All data is displayed on a dashboard as counters and statuses and updated automatically. A report that previously took hours to prepare can now be generated in 30 seconds—all you have to do is open the relevant section.
The system was developed without an in-house developer, using AI coding tools. It took one week to create a working version of the CRM with API integration.
How else you can use Action Log
Octo’s API can handle different tasks: real-time operational control, activity tracking, and internal process automation. In both cases described above, integration took hours or days rather than months and did not require a dedicated development team.
The principle is the same in every scenario: Octo sends profile activity events in real time, while the client’s system decides how to process and store them and what to use them for.
Real customer needs play a major role in the development of Action Log. Events such as profiles.transferred, profiles.deleted, and profiles.proxy_changed and others were added at the request of teams that had already built workflows around the API but lacked the necessary data. The list of events will continue to expand.
In addition to the use cases described above, Action Log can be used for other tasks:
Billing and internal calculations:
run_durationfromprofiles.stoppedprovides the exact session duration, useful if you bill employees based on their actual time working with a profile, without a separate time tracker.Deletion auditing:
profiles.deletedandprofiles.trashed, together withuser_email, let you quickly find out who deleted a specific profile and when, without having to ask the team.Profile transfer tracking:
profiles.transferredcontains both sides in a single record (the sender and recipient) so there’s no need to match data from different sources.Data-loss notifications:
profiles.force_stoppedmeans that a running profile was moved to an inactive status because it was not closed normally. Work performed in the profile at the time of the forced stop is not synchronized, so colleagues who were working with it should be warned that their changes will not be saved. Learn more in our documentation.Proxy change tracking:
proxy_assigned/proxy_unassigned/proxy_changedare suitable for scenarios where external systems need to know the profile’s current proxy without polling it periodically.
There is one important limitation: Action Log works as a stream and stores data only for the last 24 hours. So if you need events for auditing or long-term analytics, you need to save them in your own system as they arrive.
Now let’s look at the available events and how to work with them.
How Action Log in Octo Browser works
Type: WebSocket
URL:
wss://app.octobrowser.net/api/v2/automation/ws/action_logAuthorization: token in the
X-Octo-Api-Tokenheader
After connecting, the server sends events automatically as they occur—no repeated requests are required. Each message is a StreamPage: an array of events and a watermark.
{ "items": [ { "uuid": "550e8400-e29b-41d4-a716-446655440000", "action": "profiles.started", "time": 1707053400, "user_email": "user@example.com", "object_type": "profile", "object_id": "a1b2c3d4e5f67890abcdef1234567890", "object_title": "Octo Browser Profile", "data": { "connection_data": { "ip": "203.0.113.42", "countryCode": "FR", "countryName": "France", "subdivisions": ["Île-de-France", "Paris"], "postalCode": "75008", "cityName": "Paris", "timezone": "Europe/Paris", "lat": 48.8566, "lon": 2.3522, "languages": ["fr", "en-US", "en"], "isp": "Example Networks Ltd", "connectionType": "Cable/DSL" } } } ], "watermark": { "uuid": "550e8400-e29b-41d4-a716-446655440000", "time": 1707053400 } }
{ "items": [ { "uuid": "550e8400-e29b-41d4-a716-446655440000", "action": "profiles.started", "time": 1707053400, "user_email": "user@example.com", "object_type": "profile", "object_id": "a1b2c3d4e5f67890abcdef1234567890", "object_title": "Octo Browser Profile", "data": { "connection_data": { "ip": "203.0.113.42", "countryCode": "FR", "countryName": "France", "subdivisions": ["Île-de-France", "Paris"], "postalCode": "75008", "cityName": "Paris", "timezone": "Europe/Paris", "lat": 48.8566, "lon": 2.3522, "languages": ["fr", "en-US", "en"], "isp": "Example Networks Ltd", "connectionType": "Cable/DSL" } } } ], "watermark": { "uuid": "550e8400-e29b-41d4-a716-446655440000", "time": 1707053400 } }
Action Log currently supports ten event types:
profiles.started— profile launched;profiles.stopped— profile stopped;profiles.force_stopped— profile forcibly stopped;profiles.transferred— profile transferred to another user;profiles.trashed— profile moved to the trash;profiles.deleted— profile deleted;profiles.updated— profile updated;profiles.proxy_assigned— proxy assigned to profile;profiles.proxy_unassigned— proxy removed from profile;profiles.proxy_changed— proxy changed to another one.
To handle connection interruptions, each message contains a watermark — the UUID and timestamp of the latest event. When reconnecting, simply pass this UUID in the after_uuid parameter: the stream will send everything that happened during the downtime without losing or duplicating events. Another option is to start from a specific point in time using from_timestamp. There is only one limitation: events are stored for 24 hours. This is a live stream, not an archive.
This also works with bulk operations: if an employee moves 100 profiles to the trash at once, all 100 events will appear in the stream, each with its own object_id—none will be lost or duplicated.
You can learn more about how Action Log works here.
What’s in data — event-by-event breakdown
The fields inside data depend on the action. Here’s what is provided for each of the ten types:
Event | What’s in |
|
|
|
|
|
|
|
|
|
|
|
|
| the same fields with |
profiles.stopped
Session duration is provided in ISO 8601 format, rather than as a number of seconds:
{ "action": "profiles.stopped", "data": { "run_duration": "PT19.627134S" } }
{ "action": "profiles.stopped", "data": { "run_duration": "PT19.627134S" } }
profiles.updated
changeset is not a fixed list of fields, but a dynamic field: it contains only what changed this time. For example, when several profile settings are changed at once, you get:
{ "action": "profiles.updated", "data": { "changeset": { "description": { "old_value": "", "new_value": "Test action log" }, "start_pages": { "added": ["http://google.com", "http://facebook.com"], "removed": [] }, "storage_options": { "added": ["serviceworkers", "localstorage"], "removed": [] }, "bookmarks": { "added": ["https://ebay.com", "https://amazon.com"], "removed": [] }, "launch_args": { "added": ["--start-maximized"], "removed": [] }, "images_load_limit": { "old_value": null, "new_value": 10240 }, "local_cache": { "old_value": false, "new_value": true }, "fingerprint": {} } } }
{ "action": "profiles.updated", "data": { "changeset": { "description": { "old_value": "", "new_value": "Test action log" }, "start_pages": { "added": ["http://google.com", "http://facebook.com"], "removed": [] }, "storage_options": { "added": ["serviceworkers", "localstorage"], "removed": [] }, "bookmarks": { "added": ["https://ebay.com", "https://amazon.com"], "removed": [] }, "launch_args": { "added": ["--start-maximized"], "removed": [] }, "images_load_limit": { "old_value": null, "new_value": 10240 }, "local_cache": { "old_value": false, "new_value": true }, "fingerprint": {} } } }
You can see two patterns:
for individual values (
description,images_load_limit,local_cache), a pair ofold_value/new_valueis provided;for lists (
start_pages,storage_options,bookmarks,launch_args),added/removedis provided.
fingerprint is an exception: the key appears when the fingerprint has actually changed, but it is always an empty object, without details of what changed inside it. The general rule is simple: if a field has not changed, its key will not appear in changeset.
profiles.deleted
{ "action": "profiles.deleted", "data": { "manual": true } }
{ "action": "profiles.deleted", "data": { "manual": true } }
profiles.proxy_changed
Compares the old and new proxies in a single event:
{ "action": "profiles.proxy_changed", "data": { "old_proxy_uuid": "370b0ce963194f68803a812dd42db4be", "old_proxy_title": "Netlabs GB England", "old_url": "socks5://user:pass@proxy.example.com:1080", "new_proxy_uuid": "9c51385c3e2b417d9b34fc305a49d4f6", "new_proxy_title": "Netlabs GB England Towcester", "new_url": "socks5://user:pass@proxy2.example.com:1080" } }
{ "action": "profiles.proxy_changed", "data": { "old_proxy_uuid": "370b0ce963194f68803a812dd42db4be", "old_proxy_title": "Netlabs GB England", "old_url": "socks5://user:pass@proxy.example.com:1080", "new_proxy_uuid": "9c51385c3e2b417d9b34fc305a49d4f6", "new_proxy_title": "Netlabs GB England Towcester", "new_url": "socks5://user:pass@proxy2.example.com:1080" } }
profiles.proxy_assigned and profiles.proxy_unassigned return the same fields, but without the old_ / new_ prefixes — one set of fields per event rather than a comparison.
The temporary: true field is separate and it means that the proxy was added through a quick action (for example, by pasting a corresponding line) rather than assigned normally through the profile settings.
profiles.trashed and automatic deletion
profiles.trashed comes with empty data, just like force_stopped. If the profile is not restored or manually deleted, it will be automatically deleted after 72 hours. Learn more in the trash bin documentation.
profiles.transferred
A single event contains both sides:
{ "action": "profiles.transferred", "user_email": "sender@example.com", "data": { "receiver": "receiver@example.com" } }
{ "action": "profiles.transferred", "user_email": "sender@example.com", "data": { "receiver": "receiver@example.com" } }
user_email — as in any other event, this is the email of the user who performed the action, i.e. the sender of the transfer.
data.receiver — the recipient.
Connecting and reconnecting
You can connect using any client that supports WebSocket.
Using Node.js, it looks like this:
const WebSocket = require('ws'); const ws = new WebSocket('wss://app.octobrowser.net/api/v2/automation/ws/action_log', { headers: { 'X-Octo-Api-Token': 'Octo API Token' } }); ws.on('open', () => console.log('Connected!')); ws.on('message', (data) => { const parsed = JSON.parse(data.toString()); console.log(JSON.stringify(parsed, null, 2)); console.log(''); }); ws.on('error', (err) => console.log('Error:', err.message)); ws.on('close', () => console.log('Connection closed'));
const WebSocket = require('ws'); const ws = new WebSocket('wss://app.octobrowser.net/api/v2/automation/ws/action_log', { headers: { 'X-Octo-Api-Token': 'Octo API Token' } }); ws.on('open', () => console.log('Connected!')); ws.on('message', (data) => { const parsed = JSON.parse(data.toString()); console.log(JSON.stringify(parsed, null, 2)); console.log(''); }); ws.on('error', (err) => console.log('Error:', err.message)); ws.on('close', () => console.log('Connection closed'));
What each field in the received event means:
uuid — the identifier of the event itself, not the profile; unique for every record, even if several events occur within the same second;
action — the action type from the list above;
time — the event time in Unix format;
user_email — the email address of the user who performed the action;
object_type — the type of object the event relates to;
object_id — the identifier of the profile itself, separate from the event UUID;
object_title — the profile name at the time of the event;
data — details that depend on
action, as described above.
If a profile is stopped, a second event—profiles.stopped—will be sent with a new uuid but the same object_id, because it is the same profile. You can use object_id to link all events for a profile into a single history on your side—object_id does not change throughout the profile’s lifetime, from launch to deletion.
To resume the stream after a connection is interrupted, simply save the uuid from the last received watermark and pass it in after_uuid:
const WebSocket = require('ws'); const ws = new WebSocket('wss://app.octobrowser.net/api/v2/automation/ws/action_log?after_uuid=UUID', { headers: { 'X-Octo-Api-Token': 'Octo API Token' } }); ws.on('open', () => console.log('Connected!')); ws.on('message', (data) => { const parsed = JSON.parse(data.toString()); console.log(JSON.stringify(parsed, null, 2)); console.log(''); }); ws.on('error', (err) => console.log('Error:', err.message)); ws.on('close', () => console.log('Connection closed'));
const WebSocket = require('ws'); const ws = new WebSocket('wss://app.octobrowser.net/api/v2/automation/ws/action_log?after_uuid=UUID', { headers: { 'X-Octo-Api-Token': 'Octo API Token' } }); ws.on('open', () => console.log('Connected!')); ws.on('message', (data) => { const parsed = JSON.parse(data.toString()); console.log(JSON.stringify(parsed, null, 2)); console.log(''); }); ws.on('error', (err) => console.log('Error:', err.message)); ws.on('close', () => console.log('Connection closed'));
The connection will immediately send the events that occurred after this UUID and then continue working as a regular stream. You only need to specify after_uuid again after the next interruption.
If you don’t have a saved watermark — for example, after a long downtime when you only know the approximate time of the disconnection, you can start from a specific time using from_timestamp (Unix time in seconds):
const WebSocket = require('ws'); const ws = new WebSocket('wss://app.octobrowser.net/api/v2/automation/ws/action_log?from_timestamp=TIMESTAMP', { headers: { 'X-Octo-Api-Token': 'Octo API Token' } }); ws.on('open', () => console.log('Connected!')); ws.on('message', (data) => { const parsed = JSON.parse(data.toString()); console.log(JSON.stringify(parsed, null, 2)); console.log(''); }); ws.on('error', (err) => console.log('Error:', err.message)); ws.on('close', () => console.log('Connection closed'));
const WebSocket = require('ws'); const ws = new WebSocket('wss://app.octobrowser.net/api/v2/automation/ws/action_log?from_timestamp=TIMESTAMP', { headers: { 'X-Octo-Api-Token': 'Octo API Token' } }); ws.on('open', () => console.log('Connected!')); ws.on('message', (data) => { const parsed = JSON.parse(data.toString()); console.log(JSON.stringify(parsed, null, 2)); console.log(''); }); ws.on('error', (err) => console.log('Error:', err.message)); ws.on('close', () => console.log('Connection closed'));
If you’re just getting started with automation, we explain the Octo Browser API in more detail in a separate article.
Conclusion
Action Log lets you use Octo Browser events as part of your own infrastructure: send them to a CRM, access control system, analytics platform, or other internal tools. Octo tells you what happened to a profile, and you decide how to process, store, and use that data.
If you have a use case for it, tell us about it! Many of the events available today were added specifically in response to real-world tasks from teams working with the API.
Manage any number of accounts without bans, routine, and unnecessary expenses.
Would you like to try Octo Browser at discount?
Use the promo code OCTOBLOG to get 30% off any subscription. This offer is valid only for new users.
Access rights without manual verification
One of our clients has employees from different departments working with Octo Browser profiles. Each employee has their own access level through a custom extension used with Octo. Before granting the required permissions, the system needs to know who is currently working with a specific profile.
Without Action Log, there was no way to see exactly who was launching a profile. The access-level system had to rely on indirect indicators: it could detect that a profile was being used, but not who was using it.
The process changed when we added the profiles.started: user_email event to Action Log, so permissions could be applied automatically. This event → permissions connection became the foundation for most of our clients’ internal automations.
It’s a continuously running mechanism that responds to every profile launch in real time. Events are processed on the fly and stored in the clients’ own databases.
Our client needed one developer and about an hour to integrate the Action Log. Action Log was added to their existing permissions system as an additional module rather than requiring the process to be rebuilt from scratch.
Team accounting in seconds instead of hours
Another client works with a large volume of accounts using a distributed team: many people and many profiles being processed simultaneously. At this scale, it’s easy to lose sight of the overall picture: how many profiles each person is handling, how many are ready, how many have been assigned, how many have been deleted, and by whom and why.
Before switching to their own system, the team tracked everything in spreadsheets. Any snapshot of the team required manually going through tags and records. Collecting up-to-date data took anywhere from a couple of hours to a full day, and by the time the report was ready, some of the information was already outdated.
After we expanded Action Log, the client built their own CRM that receives profile events via the API and brings them together into a single overview: how many profiles each employee is working on, how many are ready, how many have been assigned, and how many have been deleted, with each action linked to the person who performed it and when.
All data is displayed on a dashboard as counters and statuses and updated automatically. A report that previously took hours to prepare can now be generated in 30 seconds—all you have to do is open the relevant section.
The system was developed without an in-house developer, using AI coding tools. It took one week to create a working version of the CRM with API integration.
How else you can use Action Log
Octo’s API can handle different tasks: real-time operational control, activity tracking, and internal process automation. In both cases described above, integration took hours or days rather than months and did not require a dedicated development team.
The principle is the same in every scenario: Octo sends profile activity events in real time, while the client’s system decides how to process and store them and what to use them for.
Real customer needs play a major role in the development of Action Log. Events such as profiles.transferred, profiles.deleted, and profiles.proxy_changed and others were added at the request of teams that had already built workflows around the API but lacked the necessary data. The list of events will continue to expand.
In addition to the use cases described above, Action Log can be used for other tasks:
Billing and internal calculations:
run_durationfromprofiles.stoppedprovides the exact session duration, useful if you bill employees based on their actual time working with a profile, without a separate time tracker.Deletion auditing:
profiles.deletedandprofiles.trashed, together withuser_email, let you quickly find out who deleted a specific profile and when, without having to ask the team.Profile transfer tracking:
profiles.transferredcontains both sides in a single record (the sender and recipient) so there’s no need to match data from different sources.Data-loss notifications:
profiles.force_stoppedmeans that a running profile was moved to an inactive status because it was not closed normally. Work performed in the profile at the time of the forced stop is not synchronized, so colleagues who were working with it should be warned that their changes will not be saved. Learn more in our documentation.Proxy change tracking:
proxy_assigned/proxy_unassigned/proxy_changedare suitable for scenarios where external systems need to know the profile’s current proxy without polling it periodically.
There is one important limitation: Action Log works as a stream and stores data only for the last 24 hours. So if you need events for auditing or long-term analytics, you need to save them in your own system as they arrive.
Now let’s look at the available events and how to work with them.
How Action Log in Octo Browser works
Type: WebSocket
URL:
wss://app.octobrowser.net/api/v2/automation/ws/action_logAuthorization: token in the
X-Octo-Api-Tokenheader
After connecting, the server sends events automatically as they occur—no repeated requests are required. Each message is a StreamPage: an array of events and a watermark.
{ "items": [ { "uuid": "550e8400-e29b-41d4-a716-446655440000", "action": "profiles.started", "time": 1707053400, "user_email": "user@example.com", "object_type": "profile", "object_id": "a1b2c3d4e5f67890abcdef1234567890", "object_title": "Octo Browser Profile", "data": { "connection_data": { "ip": "203.0.113.42", "countryCode": "FR", "countryName": "France", "subdivisions": ["Île-de-France", "Paris"], "postalCode": "75008", "cityName": "Paris", "timezone": "Europe/Paris", "lat": 48.8566, "lon": 2.3522, "languages": ["fr", "en-US", "en"], "isp": "Example Networks Ltd", "connectionType": "Cable/DSL" } } } ], "watermark": { "uuid": "550e8400-e29b-41d4-a716-446655440000", "time": 1707053400 } }
Action Log currently supports ten event types:
profiles.started— profile launched;profiles.stopped— profile stopped;profiles.force_stopped— profile forcibly stopped;profiles.transferred— profile transferred to another user;profiles.trashed— profile moved to the trash;profiles.deleted— profile deleted;profiles.updated— profile updated;profiles.proxy_assigned— proxy assigned to profile;profiles.proxy_unassigned— proxy removed from profile;profiles.proxy_changed— proxy changed to another one.
To handle connection interruptions, each message contains a watermark — the UUID and timestamp of the latest event. When reconnecting, simply pass this UUID in the after_uuid parameter: the stream will send everything that happened during the downtime without losing or duplicating events. Another option is to start from a specific point in time using from_timestamp. There is only one limitation: events are stored for 24 hours. This is a live stream, not an archive.
This also works with bulk operations: if an employee moves 100 profiles to the trash at once, all 100 events will appear in the stream, each with its own object_id—none will be lost or duplicated.
You can learn more about how Action Log works here.
What’s in data — event-by-event breakdown
The fields inside data depend on the action. Here’s what is provided for each of the ten types:
Event | What’s in |
|
|
|
|
|
|
|
|
|
|
|
|
| the same fields with |
profiles.stopped
Session duration is provided in ISO 8601 format, rather than as a number of seconds:
{ "action": "profiles.stopped", "data": { "run_duration": "PT19.627134S" } }
profiles.updated
changeset is not a fixed list of fields, but a dynamic field: it contains only what changed this time. For example, when several profile settings are changed at once, you get:
{ "action": "profiles.updated", "data": { "changeset": { "description": { "old_value": "", "new_value": "Test action log" }, "start_pages": { "added": ["http://google.com", "http://facebook.com"], "removed": [] }, "storage_options": { "added": ["serviceworkers", "localstorage"], "removed": [] }, "bookmarks": { "added": ["https://ebay.com", "https://amazon.com"], "removed": [] }, "launch_args": { "added": ["--start-maximized"], "removed": [] }, "images_load_limit": { "old_value": null, "new_value": 10240 }, "local_cache": { "old_value": false, "new_value": true }, "fingerprint": {} } } }
You can see two patterns:
for individual values (
description,images_load_limit,local_cache), a pair ofold_value/new_valueis provided;for lists (
start_pages,storage_options,bookmarks,launch_args),added/removedis provided.
fingerprint is an exception: the key appears when the fingerprint has actually changed, but it is always an empty object, without details of what changed inside it. The general rule is simple: if a field has not changed, its key will not appear in changeset.
profiles.deleted
{ "action": "profiles.deleted", "data": { "manual": true } }
profiles.proxy_changed
Compares the old and new proxies in a single event:
{ "action": "profiles.proxy_changed", "data": { "old_proxy_uuid": "370b0ce963194f68803a812dd42db4be", "old_proxy_title": "Netlabs GB England", "old_url": "socks5://user:pass@proxy.example.com:1080", "new_proxy_uuid": "9c51385c3e2b417d9b34fc305a49d4f6", "new_proxy_title": "Netlabs GB England Towcester", "new_url": "socks5://user:pass@proxy2.example.com:1080" } }
profiles.proxy_assigned and profiles.proxy_unassigned return the same fields, but without the old_ / new_ prefixes — one set of fields per event rather than a comparison.
The temporary: true field is separate and it means that the proxy was added through a quick action (for example, by pasting a corresponding line) rather than assigned normally through the profile settings.
profiles.trashed and automatic deletion
profiles.trashed comes with empty data, just like force_stopped. If the profile is not restored or manually deleted, it will be automatically deleted after 72 hours. Learn more in the trash bin documentation.
profiles.transferred
A single event contains both sides:
{ "action": "profiles.transferred", "user_email": "sender@example.com", "data": { "receiver": "receiver@example.com" } }
user_email — as in any other event, this is the email of the user who performed the action, i.e. the sender of the transfer.
data.receiver — the recipient.
Connecting and reconnecting
You can connect using any client that supports WebSocket.
Using Node.js, it looks like this:
const WebSocket = require('ws'); const ws = new WebSocket('wss://app.octobrowser.net/api/v2/automation/ws/action_log', { headers: { 'X-Octo-Api-Token': 'Octo API Token' } }); ws.on('open', () => console.log('Connected!')); ws.on('message', (data) => { const parsed = JSON.parse(data.toString()); console.log(JSON.stringify(parsed, null, 2)); console.log(''); }); ws.on('error', (err) => console.log('Error:', err.message)); ws.on('close', () => console.log('Connection closed'));
What each field in the received event means:
uuid — the identifier of the event itself, not the profile; unique for every record, even if several events occur within the same second;
action — the action type from the list above;
time — the event time in Unix format;
user_email — the email address of the user who performed the action;
object_type — the type of object the event relates to;
object_id — the identifier of the profile itself, separate from the event UUID;
object_title — the profile name at the time of the event;
data — details that depend on
action, as described above.
If a profile is stopped, a second event—profiles.stopped—will be sent with a new uuid but the same object_id, because it is the same profile. You can use object_id to link all events for a profile into a single history on your side—object_id does not change throughout the profile’s lifetime, from launch to deletion.
To resume the stream after a connection is interrupted, simply save the uuid from the last received watermark and pass it in after_uuid:
const WebSocket = require('ws'); const ws = new WebSocket('wss://app.octobrowser.net/api/v2/automation/ws/action_log?after_uuid=UUID', { headers: { 'X-Octo-Api-Token': 'Octo API Token' } }); ws.on('open', () => console.log('Connected!')); ws.on('message', (data) => { const parsed = JSON.parse(data.toString()); console.log(JSON.stringify(parsed, null, 2)); console.log(''); }); ws.on('error', (err) => console.log('Error:', err.message)); ws.on('close', () => console.log('Connection closed'));
The connection will immediately send the events that occurred after this UUID and then continue working as a regular stream. You only need to specify after_uuid again after the next interruption.
If you don’t have a saved watermark — for example, after a long downtime when you only know the approximate time of the disconnection, you can start from a specific time using from_timestamp (Unix time in seconds):
const WebSocket = require('ws'); const ws = new WebSocket('wss://app.octobrowser.net/api/v2/automation/ws/action_log?from_timestamp=TIMESTAMP', { headers: { 'X-Octo-Api-Token': 'Octo API Token' } }); ws.on('open', () => console.log('Connected!')); ws.on('message', (data) => { const parsed = JSON.parse(data.toString()); console.log(JSON.stringify(parsed, null, 2)); console.log(''); }); ws.on('error', (err) => console.log('Error:', err.message)); ws.on('close', () => console.log('Connection closed'));
If you’re just getting started with automation, we explain the Octo Browser API in more detail in a separate article.
Conclusion
Action Log lets you use Octo Browser events as part of your own infrastructure: send them to a CRM, access control system, analytics platform, or other internal tools. Octo tells you what happened to a profile, and you decide how to process, store, and use that data.
If you have a use case for it, tell us about it! Many of the events available today were added specifically in response to real-world tasks from teams working with the API.
Stay up to date with the latest Octo Browser news
By clicking the button you agree to our Privacy Policy.
Stay up to date with the latest Octo Browser news
By clicking the button you agree to our Privacy Policy.
Stay up to date with the latest Octo Browser news
By clicking the button you agree to our Privacy Policy.

Join Octo Browser now
Or contact Customer Service at any time with any questions you might have.

Join Octo Browser now
Or contact Customer Service at any time with any questions you might have.
Join Octo Browser now
Or contact Customer Service at any time with any questions you might have.
