PagerDuty
Trigger PagerDuty incidents using the Events API v2. No SDK needed — it's a single JSON POST.
Integration setup
- PagerDuty → Services → Service Directory → your service → Integrations → Add → Events API v2.
- Copy the Integration Key (a.k.a. routing key).
- Set
PAGERDUTY_ROUTING_KEYin your.env.
Define the effect
src/effects/pagerduty.ts
import { createEffect, S } from "envio";
type Severity = "info" | "warning" | "error" | "critical";
export const triggerPagerDuty = createEffect(
{
name: "triggerPagerDuty",
input: {
severity: S.string, // Severity
summary: S.string,
source: S.string,
dedupKey: S.optional(S.string),
detailsJson: S.optional(S.string), // JSON-encoded custom_details
},
rateLimit: { calls: 60, per: "minute" },
mode: "orderedAfterCommit",
},
async ({ input, context }) => {
const res = await fetch("https://events.pagerduty.com/v2/enqueue", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
routing_key: process.env.PAGERDUTY_ROUTING_KEY,
event_action: "trigger",
dedup_key: input.dedupKey,
payload: {
summary: input.summary,
source: input.source,
severity: input.severity as Severity,
custom_details: input.detailsJson
? JSON.parse(input.detailsJson)
: undefined,
},
}),
});
if (!res.ok) {
context.log.error(`PagerDuty failed: ${res.status} ${await res.text()}`);
throw new Error(`PagerDuty ${res.status}`);
}
}
);
Call it from a handler
The rindexer config…
chat:
pagerduty:
- routing_key: ${PAGERDUTY_ROUTING_KEY}
severity: critical
messages:
- event_name: Transfer
filter_expression: "from = '0x0338ce5020c447f7e668dc2ef778025ce3982662' && value >= 10"
template_inline: |
New RETH Transfer Event
from: {{from}}
to: {{to}}
amount: {{format_value(value, 18)}}
link: https://etherscan.io/tx/{{transaction_information.transaction_hash}}
…becomes:
src/EventHandlers.ts
import { RocketPoolETH } from "generated";
import { triggerPagerDuty } from "./effects/pagerduty";
import { formatUnits } from "./utils/format";
const WHALE = "0x0338ce5020c447f7e668dc2ef778025ce3982662";
RocketPoolETH.Transfer.handler(async ({ event, context }) => {
const { from, to, value } = event.params;
if (from.toLowerCase() !== WHALE || value < 10n) return;
context.effect(triggerPagerDuty, {
severity: "critical",
summary: `Whale moved ${formatUnits(value)} RETH`,
source: event.srcAddress,
// dedup_key prevents one alert per event from spamming PagerDuty if you re-run
dedupKey: event.transaction.hash,
detailsJson: JSON.stringify({
from,
to,
value: value.toString(),
link: `https://etherscan.io/tx/${event.transaction.hash}`,
}),
});
});
dedupKey is the cleanest way to guarantee at-most-one incident per transaction even across reruns.