Page the on-call engineer the moment a machine anomaly fires, with the machine ID and the reading already attached. The fastest template here: no shim, no OAuth, two API calls.
One trigger per severity band. Forge interpolates values but does not evaluate expressions, so severity is set on the tool rather than computed from the reading.
| Condition | PagerDuty severity | Opsgenie priority | Meaning |
|---|---|---|---|
| Threshold hard-breached | critical | P1 | Stop the line now |
| Breach forecast within 4h | error | P2 | Intervene this shift |
| Breach forecast within 24h | warning | P3 | Schedule maintenance |
| Anomaly detected, no threshold | info | P4 | Worth a look |
In PagerDuty: Service → Integrations → Add integration → Events API v2. Copy the Integration Key. That is your routing key, and it is the only credential you need. The Events API takes the key in the body, so no auth header is involved.
curl -sS -X POST https://forge.foundrynet.io/v1/tools \
-H "Authorization: Bearer $FORGE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "PagerDuty critical",
"description": "Page on-call for a hard threshold breach",
"url": "https://events.pagerduty.com/v2/enqueue",
"method": "POST",
"auth_type": "none",
"payload_template": {
"routing_key": "YOUR_INTEGRATION_KEY",
"event_action": "trigger",
"dedup_key": "forge-{{machine_id}}-{{field}}",
"payload": {
"summary": "{{trigger_name}}: {{field}} is {{value}} on {{machine_id}} (limit {{threshold}})",
"source": "{{machine_id}}",
"severity": "critical",
"component": "{{field}}",
"group": "{{site}}",
"class": "machine-threshold",
"custom_details": {
"machine_id": "{{machine_id}}",
"oem": "{{oem}}",
"model": "{{model}}",
"site": "{{site}}",
"canonical_field": "{{field}}",
"actual_value": "{{value}}",
"threshold": "{{threshold}}",
"detected_at": "{{timestamp}}",
"forge_trigger_id": "{{trigger_id}}"
}
}
}
}'
dedup_key is built from the machine and field, so repeat fires on the same problem group into one incident instead of paging the same person forty times. It is also the handle used to auto-resolve below.
curl -sS -X POST https://forge.foundrynet.io/v1/triggers \
-H "Authorization: Bearer $FORGE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Bearing temp critical",
"machine_id": "YOUR_MACHINE_ID",
"condition": { "field": "sensor_readings.bearing_temp", "op": "gt", "value": 95 },
"actions": [ { "tool_id": "TOOL_ID_FROM_STEP_2" } ],
"enabled": true
}'
That is the whole integration. The next normalize event that reports a bearing temperature above 95 pages your on-call.
Register a second tool identical to the first but with "event_action": "resolve" and the same dedup_key shape, then a trigger with the inverse condition.
curl -sS -X POST https://forge.foundrynet.io/v1/triggers \
-H "Authorization: Bearer $FORGE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Bearing temp recovered",
"machine_id": "YOUR_MACHINE_ID",
"condition": { "field": "sensor_readings.bearing_temp", "op": "lte", "value": 90 },
"actions": [ { "tool_id": "RESOLVE_TOOL_ID" } ],
"enabled": true
}'
Same shape, different destination. Opsgenie authenticates with a GenieKey header rather than a bearer token, which Forge sends as a plain header.
curl -sS -X POST https://forge.foundrynet.io/v1/tools \
-H "Authorization: Bearer $FORGE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Opsgenie P1",
"description": "Raise a P1 alert for a hard threshold breach",
"url": "https://api.opsgenie.com/v2/alerts",
"method": "POST",
"auth_type": "none",
"headers": { "Authorization": "GenieKey YOUR_OPSGENIE_API_KEY" },
"payload_template": {
"message": "{{trigger_name}}: {{field}} is {{value}} on {{machine_id}}",
"alias": "forge-{{machine_id}}-{{field}}",
"description": "Machine {{machine_id}} ({{oem}} {{model}}) at {{site}} reported {{field}} = {{value}} against a limit of {{threshold}} at {{timestamp}}.",
"priority": "P1",
"source": "Forge",
"entity": "{{machine_id}}",
"tags": ["forge", "machine-threshold"],
"details": {
"machine_id": "{{machine_id}}",
"canonical_field": "{{field}}",
"actual_value": "{{value}}",
"threshold": "{{threshold}}",
"site": "{{site}}",
"forge_trigger_id": "{{trigger_id}}"
}
}
}'
https://api.eu.opsgenie.com/v2/alerts if your Opsgenie account is in the European region. A region mismatch returns 401 and is easy to misread as a bad key.
alias is Opsgenie's deduplication handle, equivalent to PagerDuty's dedup_key. Close an alert by posting to /v2/alerts/{alias}/close?identifierType=alias from a second tool.
POST https://events.pagerduty.com/v2/enqueue
Content-Type: application/json
X-FoundryNet-Trigger-Id: 7b1e4c92-3d05-4a61-9f28-c4a70d1b8e33
{
"routing_key": "R0XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"event_action": "trigger",
"dedup_key": "forge-mint_7f3a91c4-sensor_readings.bearing_temp",
"payload": {
"summary": "Bearing temp critical: sensor_readings.bearing_temp is 97.2 on mint_7f3a91c4 (limit 95)",
"source": "mint_7f3a91c4",
"severity": "critical",
"component": "sensor_readings.bearing_temp",
"group": "Plant 3 / Cell B",
"class": "machine-threshold",
"custom_details": {
"machine_id": "mint_7f3a91c4",
"oem": "Haas",
"model": "VF-2",
"canonical_field": "sensor_readings.bearing_temp",
"actual_value": "97.2",
"threshold": "95",
"detected_at": "2026-07-28T18:04:11.882714+00:00",
"forge_trigger_id": "7b1e4c92-3d05-4a61-9f28-c4a70d1b8e33"
}
}
}
custom_details arrive as strings. PagerDuty treats custom details as free-form, so this is display-only and nothing rejects it. severity is a fixed enum string set on the tool, which is what the Events API expects.
To page before the threshold is crossed rather than after, use a predicted_breach condition. That path posts a fixed body to a plain webhook_url and does not run actions[], so it needs a small forwarding endpoint rather than the direct wiring above.
{
"name": "Bearing temp will breach within 4h",
"machine_id": "YOUR_MACHINE_ID",
"condition": {
"type": "predicted_breach",
"field": "sensor_readings.bearing_temp",
"threshold": 95,
"direction": "above",
"breach_within_hours": 4,
"confidence_threshold": 0.75
},
"webhook_url": "https://your-shim.example.com/forge/predicted-breach"
}
The ServiceNow template documents the forecast payload in full. Forwarding it to PagerDuty is a few lines, since the Events API needs no auth header:
ttb = body["forecast"]["time_to_breach_hours"]
severity = "error" if ttb <= 4 else "warning"
requests.post("https://events.pagerduty.com/v2/enqueue", json={
"routing_key": ROUTING_KEY,
"event_action": "trigger",
"dedup_key": f"forge-{body['machine_id']}-{body['field']}-forecast",
"payload": {
"summary": f"{body['field']} forecast to breach {body['threshold']} in {ttb}h on {body['machine_id']}",
"source": body["machine_id"],
"severity": severity,
"custom_details": body["forecast"],
},
})