SecurityScorecardEventCollector

This integration collects history events from SecurityScorecard for Cortex XSIAM.

Data Enrichment & Threat Intelligence · SecurityScorecard

Details

IDSecurityScorecardEventCollector
ProviderSecurityScorecard
CategoryData Enrichment & Threat Intelligence
From Version8.11.0
Docker Imagedemisto/fastapi:0.125.0.10158186
Supported ModulesAgentix XSIAM

README

Overview

SecurityScorecard provides security ratings and risk assessments for organizations by continuously monitoring their external attack surface. It evaluates domains across multiple security factors including network security, DNS health, patching cadence, endpoint security, and more.

This integration collects history events from SecurityScorecard for security monitoring and compliance purposes in your Cortex XSIAM environment. Each event is enriched with detailed information from the event’s detail URL.

Authentication

This integration uses API token-based authentication.

Creating an API Token

  1. In SecurityScorecard, click your profile avatar and select My Settings.
  2. Select the API tab in the left settings pane and then click Generate New API Token.
  3. Click Confirm to generate the token.
  4. Copy the token and store it securely.

Important: API Keys do not expire on their own. Creating a new token invalidates any previously created token. You will need to replace the older API key with the new one for your integrations to continue working with SecurityScorecard.

Before You Start

Before configuring the integration, ensure you have:

  1. A valid SecurityScorecard account with API access.
  2. An API token generated from your SecurityScorecard account settings.
  3. The domain identifier (scorecard identifier) you want to monitor (e.g., google.com).

Configure SecurityScorecard Event Collector in Cortex

  1. Navigate to Settings > Configurations > Automation & Feed Integrations.
  2. Search for SecurityScorecard Event Collector.
  3. Click Add instance to create and configure a new integration instance.
Parameter Description Required
Server URL The SecurityScorecard API base URL.
Default: https://api.securityscorecard.io
True
API Token The API token for authenticating with SecurityScorecard.
Generated from My Settings > API tab.
True
Scorecard Identifier The domain identifier for the scorecard to monitor.
Example: google.com
True
Fetch events Whether to automatically fetch events. False
Maximum number of events per fetch Maximum number of events to fetch per cycle.
Default: 1000
False
First fetch time How far back to fetch events on the first run.
Example: 3 days, 7 days, 1 day
Default: 3 days
False
Trust any certificate (not secure) When selected, the integration will not verify SSL certificates. False
Use system proxy settings When selected, the integration will use the system proxy settings. False
  1. Click Test to validate the connection.
  2. Click Save & exit.

Rate Limits

The SecurityScorecard API enforces rate limits to ensure system stability and prevent abuse:

  • Each client can make up to 5,000 requests per hour over a rolling 60-minute window.
  • If the rate limit is exceeded, the API returns a 429 Too Many Requests response with a Retry-After header specifying the number of seconds to wait.

How the Integration Handles Rate Limits

The integration handles rate limits gracefully in two scenarios:

  1. Rate limit on history events API: If the rate limit is hit when fetching the list of events, the integration skips the current fetch cycle and waits for the next one.

  2. Rate limit on detail URL enrichment: Each event includes a detail_url that provides additional information. If the rate limit is hit while fetching these details, the integration:

    • Sends all events that were successfully enriched to Cortex XSIAM.
    • Updates the last run checkpoint based on the last enriched event.
    • Defers remaining events to the next fetch cycle.

Event Structure

Each collected event contains the following fields:

Field Description
id Unique identifier of the event.
date Timestamp of the event (ISO 8601 format).
event_type Type of the event (e.g., issues).
group_status Status of the issue group (active or resolved).
issue_count Number of issues in the event.
total_score_impact Total score impact of the event.
issue_type The type of issue (e.g., outdated_browser, unsafe_sri_v2).
severity Severity level (low, medium, high, critical).
factor The security factor (e.g., endpoint_security, application_security).
detail_url URL for detailed event information.
detail_url_response Enriched response from the detail URL API call.

Cortex XSIAM Mapping

  • _time field is mapped from the event’s date field.
  • Vendor: SecurityScorecard
  • Product: SecurityScorecard

Deduplication

The integration uses a high-water mark deduplication strategy:

  • After each fetch cycle, the integration saves the most recent event date and the IDs of all events sharing that date.
  • On the next fetch cycle, events with those IDs are filtered out to prevent duplicates.
  • This ensures no events are missed even when multiple events share the same timestamp.

Commands

You can execute these commands from the Cortex CLI, as part of an automation, or in a playbook.

securityscorecard-get-events


Gets history events from SecurityScorecard. This command is used for developing/debugging and should be used with caution, as it can create duplicate events and exceed API rate limits.

Base Command

securityscorecard-get-events

Input

Argument Name Description Required
date_from The start time to fetch events from. Supports relative time (e.g., “3 days ago”, “1 week”) or specific absolute dates (ISO 8601 format). Optional
date_to The end time to fetch events until. Supports relative time (e.g., “now”, “1 hour ago”) or specific absolute dates (ISO 8601 format). Optional
limit Maximum number of events to retrieve. Default is 1000. Optional
should_push_events Set to true to push events to XSIAM. Use with caution to avoid duplicates. Possible values are: true, false. Default is false. Optional

Context Output

Path Type Description
SecurityScorecard.Event.id Number Unique identifier of the event.
SecurityScorecard.Event.date Date Timestamp of the event.
SecurityScorecard.Event.event_type String Type of the event.
SecurityScorecard.Event.factor String The security factor associated with the event.
SecurityScorecard.Event.severity String Severity level of the event.
SecurityScorecard.Event.issue_type String The type of issue.
SecurityScorecard.Event.group_status String Status of the issue group (active/resolved).
SecurityScorecard.Event.issue_count Number Number of issues in the event.
SecurityScorecard.Event.total_score_impact Number Total score impact of the event.
SecurityScorecard.Event.detail_url String URL for detailed event information.
SecurityScorecard.Event.detail_url_response Unknown Response from the detail URL API call.

Command Example

!securityscorecard-get-events date_from="3 days ago" limit=10

Human Readable Output

SecurityScorecard Event Collector Events

id date event_type factor severity issue_type group_status
23751008 2026-03-18T15:06:17.467Z issues endpoint_security high outdated_browser resolved
37991923 2026-03-18T15:06:17.467Z issues application_security low unsafe_sri_v2 active

Configuration parameters

  • url — Server URL (required)
  • api_token — (required)
  • scorecard_identifier — Scorecard Identifier (required)
  • isFetchEvents — Fetch Events
  • max_fetch — Maximum number of events per fetch
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (1)

  • securityscorecard-get-events

    Retrieves history events from SecurityScorecard. Use this command for development and debugging only, as it may produce duplicate events, exceed API rate limits, or disrupt the fetch mechanism.

import traceback
from datetime import datetime, timezone  # noqa: UP017
from typing import Any

import demistomock as demisto  # noqa: F401
from ContentClientApiModule import *
from CommonServerPython import *  # noqa: F401

# region Constants
INTEGRATION_NAME = "SecurityScorecard Event Collector"


class Config:
    """Global static configuration."""

    VENDOR = "SecurityScorecard"
    PRODUCT = "SecurityScorecard"

    # Date format for API requests (ISO 8601)
    DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"

    # Fetch defaults
    DEFAULT_MAX_FETCH = 1000
    DEFAULT_FIRST_FETCH = "1 minute"

    # API status codes
    RATE_LIMIT_STATUS_CODE = 429


# region Client
# =================================
# Client
# =================================


class Client(ContentClient):
    """SecurityScorecard API client for fetching history events.

    Extends ContentClient for built-in retry logic, rate-limit handling,
    structured logging, and authentication via APIKeyAuthHandler.

    Attributes:
        scorecard_identifier: The domain identifier for the scorecard (e.g., google.com).
    """

    def __init__(
        self,
        base_url: str,
        api_token: str,
        scorecard_identifier: str,
        verify: bool,
        proxy: bool,
    ):
        auth_handler = APIKeyAuthHandler(
            key=f"Token {api_token}",
            header_name="Authorization",
        )
        super().__init__(
            base_url=base_url,
            verify=verify,
            proxy=proxy,
            auth_handler=auth_handler,
            headers={"Accept": "application/json"},
            client_name="SecurityScorecardEventCollector",
            ok_codes=(200, 429),
        )
        self.scorecard_identifier = scorecard_identifier

    def get_history_events(
        self,
        date_from: str,
        date_to: str,
    ) -> list[dict[str, Any]]:
        """Fetch history events for the scorecard identifier.

        Args:
            date_from: Start date in ISO 8601 format.
            date_to: End date in ISO 8601 format.

        Returns:
            List of event entries.

        Raises:
            RateLimitError: If the API returns a 429 status code.
        """
        params = assign_params(date_from=date_from, date_to=date_to)

        demisto.debug(f"[API] Fetching history events for '{self.scorecard_identifier}' from {date_from} to {date_to}")

        response = self._http_request(
            method="GET",
            url_suffix=f"companies/{self.scorecard_identifier}/history/events",
            params=params,
            resp_type="response",
            ok_codes=(200, 429),
        )

        if response.status_code == Config.RATE_LIMIT_STATUS_CODE:
            retry_after = response.headers.get("Retry-After", "60")
            demisto.debug(f"[API] Rate limit hit on history events. Retry-After: {retry_after}")
            raise RateLimitError(retry_after=retry_after)

        response_json = response.json()
        entries = response_json.get("entries", [])
        demisto.debug(f"[API] Fetched {len(entries)} history events.")
        return entries

    def get_detail_url_response(self, detail_url: str) -> dict[str, Any]:
        """Fetch the detailed response for a given detail_url.

        Args:
            detail_url: The full URL to fetch detail data from.

        Returns:
            The JSON response from the detail URL.

        Raises:
            RateLimitError: If the API returns a 429 status code.
        """
        demisto.debug(f"[API] Fetching detail URL: {detail_url}")

        response = self._http_request(
            method="GET",
            full_url=detail_url,
            resp_type="response",
            ok_codes=(200, 429),
        )

        if response.status_code == Config.RATE_LIMIT_STATUS_CODE:
            retry_after = response.headers.get("Retry-After", "60")
            demisto.debug(f"[API] Rate limit hit on detail URL. Retry-After: {retry_after}")
            raise RateLimitError(retry_after=retry_after)

        return response.json()


# endregion


# region Helpers
# =================================
# Helpers
# =================================


class RateLimitError(Exception):
    """Raised when the API returns a 429 Too Many Requests response."""

    def __init__(self, retry_after: str = "60"):
        self.retry_after = retry_after
        super().__init__(f"Rate limit exceeded. Retry after {retry_after} seconds.")


def add_time_to_events(events: list[dict[str, Any]]) -> None:
    """Add _time field to events for XSIAM ingestion.

    Maps the event's 'date' field to '_time' for proper XSIAM indexing.
    """
    for event in events:
        event_time = event.get("date")
        if event_time:
            event["_time"] = event_time
        else:
            demisto.debug(f"[Event Time] WARNING: Event missing 'date' field: {event.get('id', 'unknown')}")


def deduplicate_events(
    events: list[dict[str, Any]],
    last_fetched_ids: list[int],
) -> list[dict[str, Any]]:
    """Remove already-processed events based on previously fetched IDs.

    Args:
        events: List of events to deduplicate.
        last_fetched_ids: List of event IDs from the previous fetch cycle.

    Returns:
        List of new events that were not previously fetched.
    """
    if not events:
        demisto.debug("[Dedup] No events to process.")
        return events

    if not last_fetched_ids:
        demisto.debug("[Dedup] No deduplication needed (first run - no previous IDs).")
        return events

    demisto.debug(f"[Dedup] Checking {len(events)} events against " f"{len(last_fetched_ids)} previously fetched IDs.")

    fetched_ids_set = set(last_fetched_ids)
    new_events = [event for event in events if event.get("id") not in fetched_ids_set]

    skipped_count = len(events) - len(new_events)
    if skipped_count > 0:
        demisto.debug(f"[Dedup] Skipped {skipped_count} duplicates. {len(new_events)} new events remain.")
    else:
        demisto.debug("[Dedup] No duplicates found.")

    return new_events


def calculate_last_run(
    events: list[dict[str, Any]],
    last_run: dict[str, Any] | None = None,
) -> dict[str, Any]:
    """Calculate the last run state from the given events.

    Saves the most recent date and the IDs of events that share that date
    for deduplication on the next run. If the most recent date matches the
    previous fetch date from last_run, the IDs are merged to prevent
    duplicate ingestion across fetch cycles.

    Args:
        events: List of events (sorted by date ascending).
        last_run: Previous last run state dictionary (optional).

    Returns:
        Dictionary with 'last_fetch' (date string) and 'last_fetched_ids' (list of ints).
    """
    if not events:
        return {}

    last_event = events[-1]
    last_date = last_event.get("date", "")

    # Collect all IDs that share the same date as the last event
    ids_at_last_date = [event.get("id") for event in events if event.get("date") == last_date and event.get("id") is not None]

    # Merge with previous IDs if the date hasn't changed
    if last_run and last_run.get("last_fetch") == last_date:
        previous_ids: list[int] = last_run.get("last_fetched_ids", [])
        merged_ids = list(set(previous_ids) | set(ids_at_last_date))
        demisto.debug(
            f"[LastRun] Same date {last_date} as previous run. "
            f"Merged {len(previous_ids)} previous + {len(ids_at_last_date)} new IDs = {len(merged_ids)} total."
        )
        ids_at_last_date = merged_ids

    demisto.debug(f"[LastRun] New high-water mark: {last_date} with {len(ids_at_last_date)} IDs.")

    return {
        "last_fetch": last_date,
        "last_fetched_ids": ids_at_last_date,
    }


def get_fetch_start_time(params: dict[str, Any], last_run: dict[str, Any]) -> str:
    """Determine the start time for fetching events.

    Uses last_run if available, otherwise falls back to first_fetch parameter.

    Args:
        params: Integration parameters.
        last_run: Last run state dictionary.

    Returns:
        ISO 8601 formatted date string.
    """
    last_fetch = last_run.get("last_fetch")
    if last_fetch:
        demisto.debug(f"[Fetch] Continuing from last run: {last_fetch}")
        return last_fetch

    first_fetch = params.get("first_fetch", Config.DEFAULT_FIRST_FETCH)
    demisto.debug(f"[Fetch] First run - using first_fetch parameter: {first_fetch}")

    first_fetch_dt = arg_to_datetime(arg=first_fetch, arg_name="first_fetch", required=True)
    if first_fetch_dt is None:
        raise DemistoException(f"Failed to parse first_fetch parameter: {first_fetch}")

    return first_fetch_dt.strftime(Config.DATE_FORMAT)


# endregion


# region Commands
# =================================
# Commands
# =================================


def test_module(client: Client) -> str:
    """Test API connectivity by fetching a small window of events.

    Args:
        client: The SecurityScorecard API client.

    Returns:
        'ok' if the test succeeds.
    """
    demisto.debug("[Test Module] Starting...")
    try:
        now = datetime.now(timezone.utc)  # noqa: UP017
        date_from = (now - timedelta(days=1)).strftime(Config.DATE_FORMAT)
        date_to = now.strftime(Config.DATE_FORMAT)

        client.get_history_events(date_from=date_from, date_to=date_to)
        demisto.debug("[Test Module] Success.")
        return "ok"

    except RateLimitError:
        # If we hit rate limit, the connection is working
        demisto.debug("[Test Module] Rate limit hit but connection is working.")
        return "ok"

    except ContentClientAuthenticationError as error:
        demisto.debug(f"[Test Module] Auth failed: {error}")
        return "Authorization Error: Verify your API Token."

    except Exception as error:
        error_msg = str(error)
        demisto.debug(f"[Test Module] Failed: {error_msg}")
        if "401" in error_msg or "403" in error_msg:
            return "Authorization Error: Verify your API Token."
        raise


def get_events_command(
    client: Client,
    args: dict[str, Any],
) -> CommandResults | str:
    """Manual command to get events for debugging/development.

    Args:
        client: The SecurityScorecard API client.
        args: Command arguments including:
            - start_time: Start time for fetching events (required).
            - end_time: End time for fetching events (optional, defaults to now).
            - event_type: Filter events by type (optional).
            - limit: Maximum number of events to retrieve (optional, default 1000).
            - should_push_events: Whether to push events to XSIAM (optional, default false).

    Returns:
        CommandResults with the events or a string message if pushed to XSIAM.
    """
    demisto.debug("[Command] get-events triggered.")

    limit = arg_to_number(args.get("limit", Config.DEFAULT_MAX_FETCH)) or Config.DEFAULT_MAX_FETCH
    should_push_events = argToBoolean(args.get("should_push_events", False))
    event_type_filter = args.get("event_type")

    start_time_input = args.get("start_time", "3 days ago")
    end_time_input = args.get("end_time")

    start_time_dt = arg_to_datetime(arg=start_time_input, arg_name="start_time", required=True)
    if start_time_dt is None:
        raise DemistoException(f"Failed to parse start_time: {start_time_input}")
    date_from = start_time_dt.strftime(Config.DATE_FORMAT)

    if end_time_input:
        end_time_dt = arg_to_datetime(arg=end_time_input, arg_name="end_time")
        if end_time_dt is None:
            raise DemistoException(f"Failed to parse end_time: {end_time_input}")
        date_to = end_time_dt.strftime(Config.DATE_FORMAT)
    else:
        date_to = datetime.now(timezone.utc).strftime(Config.DATE_FORMAT)  # noqa: UP017

    demisto.debug(
        f"[Command Params] From: {date_from}, To: {date_to}, "
        f"Limit: {limit}, EventType: {event_type_filter}, Push: {should_push_events}"
    )

    events = client.get_history_events(date_from=date_from, date_to=date_to)

    # Sort by date ascending
    events.sort(key=lambda x: x.get("date", ""))

    # Filter by event_type if specified
    if event_type_filter:
        events = [event for event in events if event.get("event_type") == event_type_filter]
        demisto.debug(f"[Command] Filtered to {len(events)} events with event_type='{event_type_filter}'.")

    # Apply limit
    if len(events) > limit:
        events = events[:limit]

    # Enrich with detail URLs (use safe version to handle rate limits gracefully)
    events, rate_limited = _safe_enrich_events(client, events)
    if rate_limited:
        demisto.debug("[Command] Rate limit hit during enrichment. Returning partial results.")

    # Always add _time field for standardized event output
    add_time_to_events(events)

    if should_push_events and events:
        send_events_to_xsiam(events=events, vendor=Config.VENDOR, product=Config.PRODUCT)
        demisto.debug(f"[Command] Pushed {len(events)} events to XSIAM.")
        return f"Successfully retrieved and pushed {len(events)} events to XSIAM."

    readable_output = tableToMarkdown(
        f"{INTEGRATION_NAME} Events",
        events,
        removeNull=True,
        headers=["id", "date", "event_type", "factor", "severity", "issue_type", "group_status"],
    )

    return CommandResults(
        readable_output=readable_output,
        outputs_prefix="SecurityScorecard.Event",
        outputs_key_field="id",
        outputs=events,
    )


def fetch_events_command(client: Client, params: dict[str, Any]) -> None:
    """Scheduled command to fetch events and send them to XSIAM.

    Handles rate limiting gracefully by sending whatever events were collected
    before the rate limit was hit, updating last_run accordingly, and waiting
    for the next fetch cycle.

    Args:
        client: The SecurityScorecard API client.
        params: Integration parameters from demisto.params().
    """
    max_fetch = arg_to_number(params.get("max_fetch", Config.DEFAULT_MAX_FETCH)) or Config.DEFAULT_MAX_FETCH

    last_run = demisto.getLastRun()
    raw_ids = last_run.get("last_fetched_ids")
    last_fetched_ids: list[int] = raw_ids if isinstance(raw_ids, list) else []

    date_from = get_fetch_start_time(params, last_run)
    date_to = datetime.now(timezone.utc).strftime(Config.DATE_FORMAT)  # noqa: UP017

    demisto.debug(
        f"[Command Params] From: {date_from}, To: {date_to}, " f"Max: {max_fetch}, Previous IDs count: {len(last_fetched_ids)}"
    )

    # Step 1: Fetch history events
    rate_limit_on_history = False
    try:
        events = client.get_history_events(date_from=date_from, date_to=date_to)
    except RateLimitError:
        demisto.debug("[Fetch] Rate limit hit on history events API. No events to process.")
        rate_limit_on_history = True
        events = []

    if not events or rate_limit_on_history:
        demisto.debug("[Fetch] No events found or rate limited on initial fetch.")
        return

    # Step 2: Sort events by date ascending
    events.sort(key=lambda x: x.get("date", ""))

    # Step 3: Deduplicate against previous run
    events = deduplicate_events(events, last_fetched_ids)

    if not events:
        demisto.debug("[Fetch] All events were duplicates. Preserving last_run with existing dedup IDs.")
        demisto.setLastRun({"last_fetch": date_from, "last_fetched_ids": last_fetched_ids})
        return

    # Step 4: Apply max_fetch limit
    if len(events) > max_fetch:
        overflow = len(events) - max_fetch
        demisto.debug(
            f"[Fetch] Overflow: API returned {len(events)} events, max_fetch={max_fetch}. "
            f"{overflow} events deferred to the next fetch cycle."
        )
        events = events[:max_fetch]

    # Step 5: Enrich events with detail URL responses
    # Use _safe_enrich_events which handles rate limits gracefully
    # and returns partial results without re-raising
    enriched_events, rate_limit_on_detail = _safe_enrich_events(client, events)

    # Step 6: Send events to XSIAM
    if enriched_events:
        add_time_to_events(enriched_events)
        send_events_to_xsiam(events=enriched_events, vendor=Config.VENDOR, product=Config.PRODUCT)
        demisto.debug(f"[Fetch] Pushed {len(enriched_events)} events to XSIAM.")

        # Step 7: Update last run based on what was actually sent
        new_last_run = calculate_last_run(enriched_events, last_run)
        if new_last_run:
            demisto.setLastRun(new_last_run)
            demisto.debug(f"[Fetch] Last run updated: {new_last_run.get('last_fetch')}")
    else:
        demisto.debug("[Fetch] No enriched events to send.")

    if rate_limit_on_detail:
        demisto.debug("[Fetch] Rate limit was hit during enrichment. " "Remaining events will be fetched in the next cycle.")


def _safe_enrich_events(
    client: Client,
    events: list[dict[str, Any]],
) -> tuple[list[dict[str, Any]], bool]:
    """Enrich events with detail URLs, stopping gracefully on rate limit.

    Args:
        client: The SecurityScorecard API client.
        events: List of events to enrich.

    Returns:
        Tuple of (list of enriched events, whether rate limit was hit).
        The list may be partial if rate limited.
    """
    enriched_events: list[dict[str, Any]] = []
    rate_limited = False

    for event in events:
        detail_url = event.get("detail_url")
        if not detail_url:
            enriched_events.append(event)
            continue

        try:
            detail_response = client.get_detail_url_response(detail_url)
            event["detail_url_response"] = detail_response
            enriched_events.append(event)
        except RateLimitError:
            demisto.debug(
                f"[SafeEnrich] Rate limit hit at event {event.get('id')}. " f"Returning {len(enriched_events)} enriched events."
            )
            rate_limited = True
            break

    return enriched_events, rate_limited


# endregion


# region Main
# =================================
# Main
# =================================


def parse_integration_params(params: dict[str, Any]) -> dict[str, Any]:
    """Parse and validate integration parameters.

    Args:
        params: Raw integration parameters from demisto.params().

    Returns:
        Dictionary with parsed parameters: base_url, api_token,
        scorecard_identifier, verify, proxy.

    Raises:
        DemistoException: If required parameters are missing.
    """
    base_url = params.get("url", "https://api.securityscorecard.io").rstrip("/")
    api_token = params.get("api_token", {}).get("password", "")
    scorecard_identifier = params.get("scorecard_identifier", "").strip()
    verify = not argToBoolean(params.get("insecure", False))
    proxy = argToBoolean(params.get("proxy", False))

    if not api_token:
        raise DemistoException("API Token is required.")
    if not scorecard_identifier:
        raise DemistoException("Scorecard Identifier is required.")

    return {
        "base_url": base_url,
        "api_token": api_token,
        "scorecard_identifier": scorecard_identifier,
        "verify": verify,
        "proxy": proxy,
    }


def main() -> None:
    """Main entry point for SecurityScorecard Event Collector integration."""
    demisto.debug(f"{INTEGRATION_NAME} integration started.")
    command = demisto.command()
    params = demisto.params()

    try:
        parsed = parse_integration_params(params)

        client = Client(
            base_url=parsed["base_url"],
            api_token=parsed["api_token"],
            scorecard_identifier=parsed["scorecard_identifier"],
            verify=parsed["verify"],
            proxy=parsed["proxy"],
        )

        if command == "test-module":
            result = test_module(client)
            return_results(result)

        elif command == "fetch-events":
            fetch_events_command(client, params)

        elif command == "securityscorecard-get-events":
            command_result = get_events_command(client, demisto.args())
            return_results(command_result)

        else:
            raise DemistoException(f"Command '{command}' is not implemented.")

    except Exception as error:
        error_msg = f"Failed to execute {command}. Error: {str(error)}"
        demisto.error(f"{error_msg}\n{traceback.format_exc()}")
        return_error(error_msg)

    demisto.debug(f"{INTEGRATION_NAME} integration finished.")


if __name__ in ("__main__", "__builtin__", "builtins"):
    main()