This guide shows how to integrate a SIEM with FortiClient EMS and FortiOS to automatically isolate endpoints when suspicious activity is detected. The SIEM triggers webhooks that call the EMS REST API to create a zero trust network access (ZTNA) tag and the FortiOS REST API to create a firewall policy that blocks affected endpoints. When the incident is resolved, cleanup webhooks remove the policy and tag, restoring access. This integration helps security teams automatically and dynamically respond to suspicious activity without manually creating tags and firewall policies.
SIEMDetects suspicious activity | vEMS REST APICreates ZTNA tag | vEMSSyncs ZTNA tag to FortiOS | vFortiOS REST APICreates firewall policy | vEndpoint isolatedIncident resolved | vFortiOS REST APIDeletes firewall policy | vEMS REST APIDeletes ZTNA tag
The integration uses the SIEM as the orchestration layer. The SIEM detects security events and invokes REST API calls through webhooks. EMS manages endpoint posture tags, while FortiOS uses synchronized tags to enforce network access policies.
This topic provides guidelines for configuring this end-to-end integration. General instructions are provided for configuring the SIEM. For specific instructions on configuring webhooks in your SIEM of choice, refer to your SIEM product documentation.
Configuring this use case consists of the following steps:
- Step 1: Configure the EMS to FortiOS Security Fabric connection
- Step 2: Create dedicated EMS and FortiOS admin users for accessing the REST APIs
- Step 3: Create a webhook in your SIEM to create the tag in EMS
- Step 4: Create a webhook in your SIEM to create a policy in FortiOS
- Step 5: Configure cleanup webhooks for incident resolution
- Step 6: Validate the integration
Prerequisites
- EMS must be running 7.4 or a later version.
- FortiClient and FortiOS versions must be compatible with EMS. See the EMS Compatibility Chart.
- HTTPS (port 443) connectivity is allowed between the SIEM, EMS, and FortiGate.
- FortiClient endpoint is connected to and compliant with EMS.
Step 1: Configure the EMS to FortiOS Security Fabric connection
Certificates
Setting up a secure connection between EMS and FortiOS requires 2 certificates:
- Webserver certificate for EMS. This can be a user-uploaded PEM, DER, or PKCS12 certificate or an ACME certificate.
In EMS, add the certificate in System Settings > EMS Server Certificates, then configure the certificate as the webserver certificate in System Settings > EMS Settings > Shared Settings. See Adding an SSL certificate to FortiClient EMS. - CA-signed certificate for FortiOS. This certificate allows FortiOS to trust the webserver certificate that you configured in EMS.
In FortiOS, go to System > Certificates, then from the Create/Import dropdown list, select CA Certificate to upload the CA-signed certificate.
FortiOS-to-EMS Fabric connector
In FortiOS, create the EMS Fabric connector.
- Go to Security Fabric > Fabric Connectors.
- Right-click FortiClient EMS, then click Edit.
- In the IP/Domain name field, enter the EMS IP address or domain name.
If EMS multitenancy is enabled, you must enter the FQDN instead of the IP address. You must enter the FQDN in the format site.fqdn to integrate the FortiGate to the specific EMS multitenancy site. For example, if the site name is sitea, enter sitea.ems.example.com. - Make sure to leave Synchronize firewall addresses enabled. This allows FortiOS to automatically create and synchronize firewall addresses for ZTNA tags received from EMS.
- Configure other fields, then click OK.
In EMS, a prompt appears to authorize the FortiOS-to-EMS connector. If the dialog doesn’t appear, go to Fabric & Connectors > Fabric Devices to authorize the connection.
Step 2: Create dedicated EMS and FortiOS admin users for accessing the REST APIs
Creating a dedicated EMS admin user for this use case is considered best practice to improve auditing, limit the scope of API access, and separate automated actions from interactive administrator activity. In EMS, go to Administrators > Admin Users and create a new super admin user. Enable access to Trusted Hosts for the new user.
For FortiOS, you must create a REST API admin to generate the API token, which must be included in the header for API requests. In FortiOS, go to System > Administrators to create a REST API admin. At the end of the process, a prompt displays with the API token. Make sure to copy and save this token securely, as it will not be displayed again. You’ll have to edit the REST API admin and regenerate the token if you do not save the original token when it first appears.
Step 3: Create a webhook in your SIEM to create the tag in EMS
You’ll now configure a webhook in your SIEM to call the EMS REST API to create a tag when a security incident is detected. Since you’ve set up the EMS to FortiOS Fabric connection in step 1, EMS will automatically share the new tag with FortiOS.
In your SIEM, configure a webhook to trigger an HTTP request when suspicious network activity is detected. Add the following EMS details:
- Destination: enter in the format
https://<EMS IP address or FQDN>/api/v1/tags/zero_trust/create. You can find the EMS IP address or FQDN in System Settings. - Method: POST
- Authentication: provide the username and password for the admin user created in Step 2.
- Headers:
Content-Type: application/json
Next, you’ll need to provide the JSON payload for the request. Here’s an example:
{ "name": "ZTNA-TAG_API_{{incident_id}}", "description": "Precautionary tag applied via API due to security incident {{incident_id}}", "status": true, "comments": "Automated webhook trigger from security detection tool.", "rules": [ { "negative": false, "content": "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Lsa\\LimitBlankPasswordUse", "value_data": "0", "type": 3, "os": 1, "id": 1 }, { "negative": true, "content": "WinDefend", "type": 5, "os": 1, "id": 2 } ], "use_custom_logic": false}
This example creates a tag with 2 rules. EMS tags endpoints that satisfy both rules. In a production environment, these rules typically represent security posture conditions defined by your organization’s incident response policy.
- Rule 1 checks the Windows registry path
HKLM\SYSTEM\CurrentControlSet\Control\Lsa\LimitBlankPasswordUseto see if the value is0. This allows network access using blank passwords, which can pose a threat in the case of network anomalies. - Rule 2 checks if Windows Defender is not active on the endpoint.
The following provides descriptions of fields of interest in the example request. For a complete list of fields and descriptions, see the EMS REST API Reference on fndn.fortinet.com.
| Field | Type | Description |
namerequired | string | Unique name of the security tag. The example uses an {{incident_id}} placeholder to add a unique dynamic identifier to each tag name that corresponds to the incident ID in the SIEM. |
descriptionrequired | string | Description of the security tag. |
statusrequired | Boolean | Enable or disable the tag immediately upon creation. |
rules.negative | Boolean | If true, rule is satisfied when condition is not present on the endpoint. If false, rule is satisfied when condition is present on the endpoint. |
rules.content | string | Primary target of the rule. In this example, rules.content has different values for each rule type (a specific registry key and Windows Defender, respectively). |
rules.value_data | string | Expected value when matching a registry key rule. Only required for registry check rules. |
rules.type | number | Unique identifier for each rule detection type. |
rules.os | number | Target operating system. 1 is for Windows endpoints. |
rules.id | number | Unique index number for the rule within this tag. |
Here’s the example JSON response for a successful request:
[ { "result": { "retval": 1, "message": "Tag 'ZTNA-TAG_API_INC-908231' created successfully." }, "data": { "id": 81, "tag_uid": "8fa2287f-9c7e-43aa-a792-6cd32b16013f", "name": "ZTNA-TAG_API_INC-908231", "type": "zero_trust", "type_id": 1, "detection_level": null, "description": "Precautionary tag applied via API due to security incident INC-908231", "update_time": "2025-07-15T21:21:45.102" } }]
The following provides descriptions of fields of interest in the example response. For a complete list of fields and descriptions, see the EMS REST API Reference on fndn.fortinet.com.
| Field | Type | Description |
result.retval | number | Execution status code. A value of 1 indicates a successful execution. |
data.id | number | Unique ID assigned by EMS to the new tag. |
data.tag_uid | string | Unique UUID assigned by EMS to the new tag. |
data.name | string | Unique name of the tag. Note that the incident ID is populated now. |
To later create the tag deletion webhook as part of the cleanup process, you must map the data.id value to a context key so that it can be referenced by the deletion webhook. In this example, extract the value to a key named EMSTagID.
Step 4: Create a webhook in your SIEM to create a policy in FortiOS
You’ll now configure a second webhook in your SIEM to call the FortiOS REST API to create a dynamic policy using the ZTNA tag address group. This policy denies endpoints tagged with the ZTNA tag from accessing the network.
In your SIEM, configure a webhook to trigger an HTTP request. This second webhook will be triggered by the first webhook, since EMS needs to create the tag before FortiOS can create the policy based on the tag. Add the following FortiOS details:
- Destination: enter in the format
https://<FortiGate IP address or FQDN>/api/v2/cmdb/firewall/policy. You can find the FortiGate IP address or FQDN in the System Information widget. - Method: POST
- Authentication: instead of providing user credentials, you’ll provide the FortiOS token in the header.
- Headers:
Authorization: Bearer <copied token>Content-Type: application/json
When configuring this webhook, add a delay of ~5 minutes to ensure that EMS syncs the tags with FortiOS before the webhook is triggered to create the policy.
Next, you’ll need to provide the JSON payload for the request. Here’s an example. Note that the source address is configured as ZTNA-TAG-API_{{incident_id}}, which will match the name of the ZTNA tag synced from EMS (which FortiOS treats as a dynamic firewall address object):
{ "name": "Block-Suspicious-Endpoints-API", "srcintf": [ { "name": "port3" } ], "dstintf": [ { "name": "any" } ], "srcaddr": [ { "name": "ZTNA-TAG_API_{{incident_id}}" } ], "dstaddr": [ { "name": "all" } ], "action": "deny", "schedule": "always", "service": [ { "name": "ALL" } ], "logtraffic": "all"}
The following provides descriptions of fields of interest in the example request. For a complete list of fields and descriptions, see the FortiOS REST API Reference on fndn.fortinet.com.
| Field | Type | Description |
name | string | Unique name for the firewall policy. |
srcintfrequired | array | Incoming interface(s). Each interface object must contain a "name" key. |
dstintfrequired | array | Outgoing interface(s). Each interface object must contain a "name" key. |
srcaddrrequired | array | Source address object(s) or dynamic ZTNA tag address(es). This example configures the source address as ZTNA-TAG-API_{{incident_id}}, which matches the name of the tag synced from EMS. |
dstaddrrequired | array | Destination address object(s). Each destination address object must contain a "name" key. |
actionrequired | string | Action taken on matching traffic. Accepted values: "accept" or "deny". |
schedulerequired | string | Schedule during which the policy is active. |
servicerequired | array | Allowed services or ports. Must map to existing service objects. |
Here’s the example JSON response for a successful request:
{ "http_method": "POST", "results": [ { "mkey": "14", "status": "success", "code": 0, "message": "Entry added successfully." } ], "vdom": "root", "path": "firewall", "name": "policy", "status": "success", "http_status": 200}
The following provides descriptions of fields of interest in the example response. For a complete list of fields and descriptions, see the FortiOS REST API Reference on fndn.fortinet.com.
| Field | Type | Description |
results.mkey | string | Match key/policy ID assigned to this newly created policy. |
To later create the policy deletion webhook as part of the cleanup process, you must map the results.mkey value to a context key so that it can be referenced by the deletion webhook. In this example, extract the value to a key named FOSPolicyID.
Step 5: Configure cleanup webhooks for incident resolution
Configuring cleanup webhooks for when the incident is resolved is highly recommended to avoid rule bloat and to allow affected endpoints to regain access to the network.
Delete the FortiOS firewall policy
Configure a webhook to trigger an HTTP request to delete the previously created policy when the incident is resolved. Add the following FortiOS details:
- Destination: enter in the format
https://<FortiGate IP address or FQDN>/api/v2/cmdb/firewall/policy/{{FOSPolicyID}}. You can find the FortiGate IP address or FQDN in the System Information widget. The {{FOSPolicyID}} is the unique policy ID that FortiOS assigned to the policy, which you saved to a context key from the JSON response body. In this example, the mkey is 14. - Method: DELETE
- Authentication: instead of providing user credentials, you’ll provide the FortiOS token in the header.
- Headers:
Authorization: Bearer <copied token>Content-Type: application/json
Here’s the example JSON response for a successful request:
{ "http_method": "DELETE", "results": [ { "status": "success", "code": 0, "message": "Entry deleted successfully." } ], "vdom": "root", "path": "firewall", "name": "policy", "status": "success", "http_status": 200}
Delete the ZTNA tag in EMS
Configure a webhook to trigger an HTTP request that is triggered by the FortiOS policy deletion webhook, since it is best practice to first delete the policy, then the tag. While deleting the policy is sufficient to restore network access to the endpoint, deleting the tag as well is considered best practice for improved network hygiene. Add the following EMS details:
- Destination: enter in the format
https://<EMS IP address or FQDN>/api/v1/tags/zero_trust/{{EMSTagID}}. You can find the EMS IP address or FQDN in System Settings. The {{EMSTagID}} is the unique tag ID that EMS assigned to the tag, which you saved to a context key from the JSON response body. In this example, the tag ID is 81. - Method: DELETE
- Authentication: provide the username and password for the admin user created in Step 2.
- Headers:
Content-Type: application/json
Here’s the example JSON response for a successful request:
{ "result": { "retval": 1, "message": "Tag deleted successfully." }}
Step 6: Validate the integration
To validate the end-to-end integration, you can simulate a network event to trigger the webhooks.
- Before triggering the event, verify that the SIEM, EMS, and FortiOS are all operating at a clean state.
- On the test endpoint, configure the settings that the EMS tag rules check for. In this example, the test endpoint is configured to satisfy the following rules:
- Rule 1: checks the Windows registry path
HKLM\SYSTEM\CurrentControlSet\Control\Lsa\LimitBlankPasswordUseto see if the value is0. - Rule 2: checks if Windows Defender is not active on the endpoint.
- Rule 1: checks the Windows registry path
- In the SIEM, simulate a security event to trigger the configured webhooks.
- Validate that the EMS tag creation webhook was successful:
- In the SIEM logs, confirm that the POST request was successful.
- In EMS, confirm that the new tag was created in Security Posture Tags > Tags.
- In EMS, view the endpoints list and confirm that the new tag is applied to the test endpoint.
- In FortiOS, validate the tag is synced from EMS. Go to Policy & Objects > Addresses. Confirm that a new address object with the same name as the tag is populated with the test endpoint IP address.
- Validate the FortiOS policy creation webhook:
- In the SIEM logs, confirm that the POST request was successful.
- In FortiOS, confirm that the new policy was created in Policy & Objects > Firewall Policy.
- On the test endpoint, attempt to access the network and confirm that access is blocked.
- In the SIEM, resolve the incident.
- Validate the FortiOS policy deletion webhook:
- In the SIEM logs, confirm that the DELETE request was successful.
- In FortiOS, confirm that the new policy has been deleted from Policy & Objects > Firewall Policy.
- On the test endpoint, attempt to access the network and confirm that access is allowed. Note that the endpoint can access the network while it is still tagged with the ZTNA tag.
- Validate the EMS tag deletion webhook:
- In the SIEM logs, confirm that the DELETE request was successful.
- In EMS, confirm that the new tag has been deleted from Security Posture Tags > Tags.
Troubleshooting
Errors when configuring this integration most commonly arise from authentication, EMS/FortiOS sync delay, or the cleanup webhooks not receiving the policy or tag IDs.
See the following for a list of common errors you might encounter in this process:
| Error | Cause(s) | Fix |
| No configuration changes occur on the EMS or FortiGate when webhooks are triggered. | Authentication, access | Check authentication details entered for the webhook (EMS credentials or FortiGate) are correct and active. If credentials are correct, confirm that ports are open on the EMS and FortiGate to allow API requests from SIEM. By default, both EMS and FortiGate receive API requests via port 443. Confirm that your requests contain all required fields and are configured correctly. |
| Policy is created on FortiOS, but affected endpoints can access the network. | Misconfigured policy | Confirm that the policy is configured correctly in FortiOS. Confirm that the policy is applied to the desired endpoints. Confirm that the policy addresses the current address group/ZTNA tag. |
| Incident is resolved in SIEM and the cleanup webhooks were triggered, but endpoints remain blocked from the network. | Misconfigured webhook | If policy still exists in FortiOS, confirm that the cleanup webhook can pass saved policy ID/mkey value correctly to the API call. If policy has been deleted from FortiOS, confirm that no other policy is applied to the endpoints that block their access from the network. |
Common API error codes and fixes
EMS
| Error | Description | Fix |
| 400 Bad Request | Request to create or delete tag fails immediately. | Confirm that your request contains all required fields and is formatted correctly. |
| 403 Forbidden | REST API admin user doesn’t have the permissions to perform this action. | Configure the specified REST API admin as a super admin in EMS. |
On EMS, even if the HTTP layer request itself succeeds, the EMS internal application engine may fail to commit the action. This is communicated using the retval field value in the JSON response:
| Error | Description | Fix |
| -3 | Entry Not Found During cleanup, EMS cannot find the tag to delete. It may already be deleted, or the tag ID was not passed correctly. | If the tag was already deleted from EMS, consider whether to treat the 404 as a success or investigate further as per your scenario. If tag still exists in EMS, confirm that the cleanup webhook can pass saved tag ID value correctly to the API call. |
| -5 | Duplicate Entry / Already Exists EMS cannot create the tag because a tag with the same name already exists. | Consider adding more incident details to the tag name to ensure unique tag names. |
FortiOS
| Error | Description | Fix |
| 400 Bad Request | Request to create or delete policy fails immediately. | Confirm that your request contains all required fields and is formatted correctly. |
| 424 Failed Dependency | FortiOS cannot reference the tag from EMS because it hasn’t received it yet. | Add a delay of ~5 minutes to trigger the policy creation webhook to allow EMS to sync the new tag to FortiOS. |
| 404 Not Found | During cleanup, FortiOS cannot find the policy to delete. It may already be deleted, or the mkey/policy ID was not passed correctly. | If the policy was already deleted from FortiOS, consider whether to treat the 404 as a success or investigate further as per your scenario. If policy still exists in FortiOS, confirm that the cleanup webhook can pass saved policy ID/mkey value correctly to the API call. |