Using decided instead of actiond. Calls made from rest calls from a playbook or custom function. This is not recommended because it does not utilize the concurrent action limits in actiond. All rest calls should be made from a SOAR app. This is also the preferred method because apps can encrypt API credentials.
Concurrent action limits need to be increased. When increasing the action limit, consider the following factors:
Do I have the server resources available to increase concurrency?
Does the destination server have the ability to accept concurrent requests?
Python runners need to be increased. The same python runner is reserved for all child playbooks that are executed by a parent playbook. If a SOAR environment creates 5 containers a minute and it takes 1 minute to run the playbook automation, then 5 python runners would be required to keep up with the demand.
Querying the SOAR PostgreSQL database instead of running Splunk queries against the phantom_* indexes. Splunk is designed to process large volumes of data. Offload queries to Splunk when possible. The following data can be found in phantom indexes:
action run
app
app run
artifact
asset
container
container note
custom function
decided list
note
playbook
workflow notes.
Not having error handling in place. After every action block, there should be a decision block to check the HTTP code of the API request. If the status is not 200, tag the container with “error” and write a comment to the container on why it failed. The phantom indexes can be monitored in Splunk to see what actions failed on which containers and why.
Creating a large playbook with multiple branches instead of leveraging children playbooks and input playbooks.
Not saving results to the container. Always save the results from an action to a container as an artifact if you will need it for downstream playbooks or reporting in Splunk.
Not having a plan in place when restarting the SOAR environment:
Have ingest sources been paused?
How will I forward and run events that were not imported during restart?
Have all active actions completed before restarting?
Using the classic VPE instead of the new VPE in latest SOAR releases. New VPE provides metrics on action blocks execution time and can identify long running actions.
Monitor event creation in phantom_* indexes using SPL and alert on any delays or ingest drops using frequently triggered alerts in Splunk. SPL can use stats or tstats commands.
Playbook Usage
Playbook Design
Playbook Development
Playbook CICD
Follow the Splunk SOAR CICD Best Practices documentation. Most importantly:
use separate development and production environments
use separate git branches for each environment
only develop in the development environment
promote playbooks between environments using git
only the development environment has read-write access to playbooks
all other environments have read-only access to playbooks
maintain repo name between environments to minimize playbook reference changes when moving between environments
have a process for mapping assets in different environments
Playbook Performance
App and Assets
Helpful Splunk Queries
index=phantom_action_run
| rename playbook as playbook_id
| join type=left left=main right=playbook where main.playbook_id = playbook.id
[search index=phantom_playbook earliest=1 latest=now ]
| table _time main.playbook_run main.container main.action playbook.name main.status main.playbook_id main.name main.id
| rename playbook.name as playbook_name
| rename main.* as * playbook.* as *
| transaction playbook_run id action
| fields playbook_id playbook_name playbook_run, id, container action name status duration eventcount
| search duration > 0
| sort - duration
| rename id as action_run_id playbook_run as playbook_run_id
index=phantom_action_run status IN (success,failed)
| rename playbook as playbook_id
| stats latest(close_time) AS close_time, latest(create_time) AS create_time BY playbook_id, playbook_run, status
| eval close_time = strptime(close_time, "%Y-%m-%dT%H:%M:%S")
| eval create_time = strptime(create_time, "%Y-%m-%dT%H:%M:%S")
| eval pb_running_for = floor(close_time - create_time)
| join [search index=phantom_playbook earliest=1 latest=now
| rename id as playbook_id | table playbook_id name]
| stats avg(pb_running_for) as avg_playbook_run_duration_secs by name status
index=phantom_action_run earliest=-2d latest=-1h
| rename playbook as playbook_id
| rename id as action_run_id
| rename container as container_id
| stats list(status) as status by action_run_id playbook_id container_id
| join [search index=phantom_playbook earliest=1 latest=now
| rename id as playbook_id | table playbook_id name]
| table action_run_id playbook_id name status container_id
| search (action_status!=success AND action_status!=failed)
index=phantom_container earliest=-3d latest=-1h
| eval container_update_time=""
| dedup id
| timechart count(id) by container_label span=1d
index=phantom_app_run "waiting for an ongoing action"
| stats count by app_name
| tstats count WHERE index=phantom_container
index=phantom_container
| eval indextime=strftime(_indextime,"%Y-%m-%d %H:%M:%S")
| eval gap = (_time - _indextime)
| table id _time indextime gap
| sort _time
| Revision | Publish Date | Comments |
|---|---|---|
1.0 |
29-Sep-2026
|
Initial Release |