Welcome to the third and final part of our Security Command Center (SCC) blog series!
So far, we’ve covered the basics of SCC, including how to trigger and resolve findings. In this final part, we’ll focus on how to propagate findings to your security team. While the strategies for achieving this can vary widely, we’ve chosen Microsoft Teams as our solution. This approach allows security alerts to seamlessly integrate into existing communication workflows, enabling incident discussions to take place directly on the same platform where the alerts appear.
Prerequisite: Set Up a Microsoft Teams Webhook
Before diving into the implementation, you’ll need to create a webhook in Microsoft Teams. This webhook enables external systems to post messages into a Teams channel or chat.
We recommend using Microsoft Power Automate (Workflows) for this. Power Automate offers ready-made templates, making it easy to set up a webhook. After selecting a template, copy the generated webhook URL – you’ll need it later.
(Note: Detailed setup steps are beyond the scope of this article, but Microsoft provides excellent documentation on this.)
Overview of the Architecture
We will implement the following configuration to archive this information: Security findings will be continuously exported to their own Pub/Sub topic. A Cloud Run function will execute a script each time a new finding is published. This script will extract all relevant information from the finding in the Pub/Sub topic and send it to Microsoft Workflows, which will then generate a notification in Teams.
Here’s a high-level diagram:

Let’s get started!
Let’s begin with creating a new (or choose a existing) a Pub/Sub Topic. Afterwards go back to the SCC:
1. Navigate to the Risk Overview Page
2. Click SETTINGS
3. Navigate to the Continous Export Tab
Fill out the needed field. By modifying the prefilled query, you can choose which findings you wish to export.
Now, search for or navigate to Cloud Run Functions

Click on Write a function and fill out all needed fields.
1. Choose a Service Name
2. Select a Region
3. for Runtime select Python 3.13
4. for Trigger you should select Pub/Sub. Choose the Event Type “google.cloud.pubsub.topic.v1.messagePublished”. Afterwards, select the corresponding Pub/Sub Topic.
5. Enable the Cloud Identity-Aware Proxy API if prompted to
6. As Ingress you should only allow internal Traffic
7. Everything else you can leave as is
Now, you should see a simple ”Hello, World” Cloud-Run-Function example:

Replace this example in the main.py file with the following code – don’t worry we’ll explain what exactly this function does below!
import requests
import json
import functions_framework
import base64
@functions_framework.http
def send_to_teams(request):
# Extract and validate JSON payload
request_json = request.get_json(silent=True)
# Decode Base64-encoded data and parse JSON
decoded_data = base64.b64decode(request_json['message']['data'])
data = json.loads(decoded_data)
# Microsoft Teams webhook URL (replace with your actual webhook)
teams_webhook_url = "https://<your-webhook>"
# Extract relevant fields from the finding data
category = data['finding']['category']
externalUri = data['finding']['externalUri']
recommendation = data['finding']['sourceProperties'].get('Recommendation', 'No recommendation provided')
explanation = data['finding']['sourceProperties'].get('Explanation', 'No explanation provided')
eventTime = data['finding'].get('eventTime', 'Unknown')
severity = data['finding'].get('severity', 'Unknown')
findingClass = data['finding'].get('findingClass', 'Unknown')
resourceName = data['resource'].get('name', 'Unknown')
resourceDisplayName = data['resource'].get('displayName', 'Unknown')
resourceType = data['resource'].get('type', 'Unknown')
location = data['resource'].get('location', 'Unknown')
organization = data['resource'].get('gcpMetadata', {}).get('organization', 'Unknown')
# Create the Adaptive Card payload
adaptive_card = {
"type": "message",
"attachments": [
{
"contentType": "application/vnd.microsoft.card.adaptive",
"content": {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.4",
"msteams": {
"width": "Full"
},
"body": [
{
"type": "Container",
"items": [
{
"type": "TextBlock",
"text": f"**Security Finding: {category}**",
"wrap": True,
"weight": "Bolder",
"size": "Large",
"color": "Attention"
},
{
"type": "FactSet",
"facts": [
{"title": "Severity", "value": f"**{severity}**"},
{"title": "Finding Type", "value": findingClass},
{"title": "Resource Type", "value": resourceType},
{"title": "Resource", "value": resourceName},
{"title": "Display Name", "value": resourceDisplayName},
{"title": "Location", "value": location},
{"title": "Organization", "value": organization},
{"title": "Time", "value": eventTime}
]
},
{
"type": "TextBlock",
"text": "---",
"separator": True
},
{
"type": "TextBlock",
"text": "**Explaination:**",
"wrap": True,
"spacing": "2rem"
},
{
"type": "TextBlock",
"text": explanation,
"wrap": True,
"spacing": "1rem"
},
{
"type": "TextBlock",
"text": "**Recommendation:**",
"wrap": True,
"spacing": "2rem"
},
{
"type": "TextBlock",
"text": recommendation,
"wrap": True,
"spacing": "1rem"
},
],
}
],
"actions": [
{
"type": "Action.OpenUrl",
"title": "🔗 View more details",
"url": externalUri
}
]
}
}
]
}
# Send the POST request to Microsoft Teams
resp = requests.post(
teams_webhook_url,
headers={'Content-Type': "application/json"},
data=json.dumps(adaptive_card)
)
return "OK", 200
Also, change the requirements.txt:
functions-framework==3.*
requests
In the Entrypoint Field, type in the name of the function: send_to_teams
Finally save and redeploy.
Explanation of the Cloud Run Function
Let’s take a closer look at the Python function that powers the integration between SCC and Microsoft Teams. This Cloud Run Function is triggered whenever a new security finding is published to the specified Pub/Sub topic. It parses the incoming message and sends an alert to Teams channel using an Adaptive Card.
1. Importing Required Modules
import requests
import json
import functions_framework
import base64
We begin by importing all the necessary Python libraries:
– requests is used to send HTTP POST requests to the Teams webhook.
– json helps us parse and construct JSON payloads.
– functions_framework is required for deploying the function to Cloud Run.
– base64 allows decoding of the encoded Pub/Sub message data.
2. Function Declaration and Request Parsing
@functions_framework.http
def send_to_teams(request):
request_json = request.get_json(silent=True)
decoded_data = base64.b64decode(request_json['message']['data'])
data = json.loads(decoded_data)
The function send_to_teams is designed to handle HTTP requests. It starts by extracting the JSON payload from the Pub/Sub event. Since Pub/Sub messages are base64-encoded, we decode the message and then parse it into a standard JSON object.
3. Extracting Data from the Security Finding
category = data['finding']['category']
externalUri = data['finding']['externalUri']
recommendation = data['finding']['sourceProperties'].get('Recommendation', 'No recommendation provided')
...
organization = data['resource'].get('gcpMetadata', {}).get('organization', 'Unknown')
This block extracts all the key pieces of information from the finding:
– The type of finding, severity, and time of the event.
– The affected resource’s name, location, and display name.
– Additional metadata like recommendations, explanations, and a link to external documentation.
Default values are provided in case some fields are missing.
4. Creating the Adaptive Card Payload
adaptive_card = {
"type": "message",
"attachments": [ ... ]
}
This snippet constructs an Adaptive Card payload — a rich card format supported by Microsoft Teams. The card includes:
– A highlighted title with the category of the security finding.
– A FactSet that clearly lists all relevant metadata.
– Sections for an explanation and recommendation.
– A link button that lets users view more details about the finding directly in GCP.
Using an Adaptive Card improves readability and ensures the message is easy to digest at a glance.
5. Sending the Alert to Microsoft Teams
resp = requests.post(
teams_webhook_url,
headers={'Content-Type': "application/json"},
data=json.dumps(adaptive_card)
)
Once the message is ready, the function sends it to the pre-configured Microsoft Teams webhook. This allows the security team to see real-time alerts directly in their communication channel.
6. Responding to the Trigger
return "OK", 200
Finally, the function returns a 200 status to acknowledge successful processing of the event.
Outcome: Teams Channel Alerts
All your SCC findings will from now on be forwarded to Teams. You can use the example on how to trigger alerts for testing from the second part of our SCC blog series to confirm everything is working.
In Teams alerts will look like this:

Conclusion
With this final integration, your security workflow is now complete: Security findings from Google Cloud’s Security Command Center are automatically delivered to your communication hub – Microsoft Teams. This allows your security team to respond immediately, collaborate on incidents in real time, and take action without disrupting their workflow.
Whether it’s a high-priority alert or part of a broader investigation, this setup enhances visibility, speeds up response times, and fosters seamless collaboration between security and DevOps teams. From now on, your security alerts won’t be missed – they’ll reach the right people, at the right time.
Thanks for following along with our Security Command Center blog series. With these integrations in place, your team is now better equipped to detect, prioritize, and respond to threats in a streamlined, collaborative environment.
If you have any questions or need help setting this up, don’t hesitate to reach o
