GCP Cloud Monitoring — Ingest Setup

This guide shows how to route GCP Cloud Monitoring alerts to Culprit using a Webhook with token notification channel.


Endpoint

POST https://ingest.theculprit.ai/ingest/<tenant_id>/<service_name>/gcp-monitoring?token=<ingest_token>

Replace <tenant_id> and <service_name> with the values shown on your service's settings page, and <ingest_token> with your ingest token.


Authentication

GCP Cloud Monitoring "Webhook with token" notification channels embed the token in the URL as ?token=<ingest_token>. Custom HTTP headers are not supported.

GCP also appends its own &auth_token=<value> to the URL — Culprit reads only ?token= and ignores the auth_token parameter.

Treat the full notification channel URL as a secret. Anyone who can read the URL can post to your endpoint. Keep it out of source control, access logs, and publicly readable dashboards.

Getting your ingest token:

  1. Open the Culprit dashboard → Services → select the service.
  2. In the Ingest token section, click Issue.
  3. Copy the token shown — it is displayed once. After you navigate away, only the token prefix is shown.

To rotate a compromised token, click Rotate. The previous token remains valid for the grace period shown, giving you time to update the notification channel URL without an interruption.


Setup in GCP

  1. In the GCP Console, go to Monitoring → Alerting → Notification channels.
  2. Click Add new and select Webhook.
  3. Set the Endpoint URL to your Culprit endpoint, including the ?token= query string shown above.
  4. GCP will display the URL with its own &auth_token= appended — this is expected. Culprit ignores that field.
  5. Click Test Connection to send a test payload and confirm the endpoint is reachable, then save the channel.
  6. Attach the notification channel to your alert policies. When a policy fires or resolves, GCP posts the incident payload to the endpoint.

How Culprit maps GCP Cloud Monitoring alerts

Culprit reads the incident object from the GCP Monitoring webhook payload.

Field mapping

| GCP field | Culprit event field | Notes | |---|---|---| | incident.condition_name | event_type | Falls back to policy_name, then gcp.alert | | incident.severity | severity | CRITICAL/ERROR/WARNING mapped (see table below) | | incident.summary | message | Human-readable description of the alert condition | | incident.incident_id | context.incident_id | GCP incident identifier | | incident.state | context.state | open or closed | | incident.url | context.url | Link to the GCP Monitoring incident detail | | incident.policy_name | context.policy_name | Alert policy that fired | | incident.threshold_value | context.threshold_value | Configured threshold | | incident.observed_value | context.observed_value | Measured value at alert time | | incident.started_at | context.started_at | Unix timestamp | | incident.ended_at | context.ended_at | Unix timestamp, null while incident is open | | incident.resource | context.resource | GCP resource descriptor (type + labels) | | (entire payload) | context.raw | Full original payload, preserved encrypted |

Severity mapping

| GCP severity | Culprit severity | |---|---| | CRITICAL | critical | | ERROR | high | | WARNING | medium | | no severity (or absent) | low |

When GCP does not assign a severity (the value is the literal string "no severity" or the field is absent), Culprit maps it to low.

state: open and closed

GCP sends a notification when an incident opens (state: "open") and again when it closes (state: "closed"). Culprit vaults both notifications. The closed notification flows through the standard pipeline; your alert rules and notification destinations can filter on context.state or the low severity (GCP typically sends no severity on closure).

PII in any field (hostnames, IP addresses, email addresses, API keys) is detected and tokenized before any storage or notification. The original values are preserved encrypted and are visible only in the incident detail view after authentication.


Worked example

GCP Cloud Monitoring fires an alert. The notification channel delivers this body:

{
  "version": "1.2",
  "incident": {
    "incident_id": "0.opqiw61fsv7p",
    "state": "open",
    "started_at": 1577840461,
    "ended_at": null,
    "severity": "CRITICAL",
    "summary": "CPU utilization for gke-cluster-1-... is above the threshold of 0.9 with a value of 0.95.",
    "condition_name": "VM Instance - CPU utilization [MAX]",
    "policy_name": "Monitor-Project-Cluster",
    "url": "https://console.cloud.google.com/monitoring/alerting/incidents/0.opqiw61fsv7p",
    "resource": { "type": "gce_instance", "labels": { "zone": "us-central1-a" } },
    "threshold_value": "0.9",
    "observed_value": "0.95"
  }
}

Culprit event (before tokenization):

{
  "event_type": "VM Instance - CPU utilization [MAX]",
  "severity": "critical",
  "source": "gcp-monitoring",
  "message": "CPU utilization for gke-cluster-1-... is above the threshold of 0.9 with a value of 0.95.",
  "context": {
    "incident_id": "0.opqiw61fsv7p",
    "state": "open",
    "url": "https://console.cloud.google.com/monitoring/alerting/incidents/0.opqiw61fsv7p",
    "policy_name": "Monitor-Project-Cluster",
    "threshold_value": "0.9",
    "observed_value": "0.95",
    "started_at": 1577840461,
    "ended_at": null,
    "resource": { "type": "gce_instance", "labels": { "zone": "us-central1-a" } }
  }
}

This event flows through the standard pipeline: encrypted vault storage → PII tokenization → storm check → embedding → correlation → root-cause analysis.


Successful response

{ "accepted": true, "ids": ["<vault_id>"] }

Error responses

| Status | Body | Meaning | |---|---|---| | 401 | { "error": "missing_auth" } | No ?token= query parameter | | 401 | { "error": "invalid_ingest_token" } | Token is incorrect or has been rotated/revoked | | 400 | { "error": "unrecognized_payload" } | Body is not valid JSON, or the incident object is absent or has no condition_name or summary | | 403 | { "error": "service_disabled" } | The service exists but monitoring is paused | | 404 | { "error": "service_deleted_or_not_found" } | The service does not exist | | 429 | { "error": "rate_limited" } | Storm protection active (sustained high volume) |