Webhook notifications to your phone
A webhook fires. You find out when you check the logs.
The fix is routing the webhook through something that sends to your phone. Here’s how to do it without adding infrastructure.
The direct approach: curl
If the webhook service lets you add custom headers or a body transformation, you can point it straight at trigger.fyi:
# From any shell script or CI step
curl -X POST "https://trigger.fyi/$TRIGGER_FYI_SECRET_KEY" \
-H "Content-Type: text/plain" \
-d "Deployment completed"That’s the whole API. POST, plain text body, done. Your phone notifies.
Some services — GitHub Actions, for example — let you add a curl step in your workflow. Others, like Stripe, need a real endpoint to verify signatures. For those, you add one function.
Receiving a webhook and forwarding it
Most webhooks need signature verification. The pattern is: verify → parse → notify.
import fyi from "trigger.fyi"
import crypto from "crypto"
export async function POST(req) {
const body = await req.text()
const signature = req.headers.get("x-webhook-signature")
// Verify the signature from your provider
const expected = crypto
.createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(body)
.digest("hex")
if (signature !== `sha256=${expected}`) {
return new Response("Unauthorized", { status: 401 })
}
const event = JSON.parse(body)
// Notify — never on the critical path
const response = new Response("OK", { status: 200 })
await fyi(event.type, { body: event.id })
return response
}This example is generic — swap in the signature logic for your provider. Stripe uses stripe-signature, GitHub uses x-hub-signature-256, Shopify uses x-shopify-hmac-sha256. The pattern is the same.
Specific providers
GitHub webhooks
Route push events, PR merges, or release publishes:
if (event.action === "closed" && event.pull_request.merged) {
fyi(`PR merged: ${event.pull_request.title}`, {
body: `by ${event.pull_request.user.login}`,
repo: event.repository.name
})
}Shopify order notifications
Shopify doesn’t have a native “send to developer phone” option. A webhook endpoint does:
if (topic === "orders/create") {
const order = event
fyi(`Order #${order.order_number}`, {
body: `$${order.total_price} · ${order.line_items.length} items`,
customer: order.email
})
}Linear issue assignments
if (event.action === "update" && event.data.assignee?.id === YOUR_USER_ID) {
fyi(`Assigned: ${event.data.title}`, {
body: event.data.identifier,
priority: event.data.priority
})
}Metadata is filterable
Every key in the second argument becomes filterable in the feed:
fyi("Order created", {
body: "$49.00",
customer: "[email protected]",
country: "US",
source: "shopify"
})Open the feed and filter by source: shopify to see only Shopify events. Or country: US. Parameters are ANDed — source: shopify country: US shows only US Shopify orders.
Levels
Match the notification level to the event weight:
// Informational — record in feed, no push
fyi.log("Webhook received", { type: event.type })
// Normal — push to phone
fyi("Order created", { body: order.email })
// Urgent — push with high priority
fyi.critical("Payment failed", { body: customer.email })On iOS, fyi.critical() delivers at normal priority — iOS web push can’t break silent mode. On Android, urgency high is honored. The behavior is documented; the code is the same.
Setting up
npx trigger.fyiGenerates a key, subscribes your device, opens a live feed in your terminal. Then in your environment:
TRIGGER_FYI_SECRET_KEY=your_key_here
The same key that the CLI uses. One env var per service, one notification stream per key. If you want separate streams for Stripe vs GitHub, use separate keys.