Webhook Integration Guide
Connect PingParrot to Slack, Teams, Zapier, or any HTTP endpoint by adding a webhook URL in your dashboard settings — PingParrot then sends an HTTP POST to that URL the instant a page is sent, acknowledged, cancelled, or escalated, formatted for Slack and Teams natively and as plain JSON everywhere else.
By Josh Crann · Updated
Overview
This follows the same request/response pattern Stripe's webhooks use: one URL, JSON payloads, an event type per request.
Native
Slack and Teams are natively supported — no middleware needed. PingParrot formats the message in each platform's own format.
Generic JSON
For custom apps, Zapier, Make, n8n, or any HTTP endpoint. The full page payload is sent as JSON.
Secure
Generic webhooks support HMAC-SHA256 signatures so your server can verify requests genuinely came from PingParrot.
Adding a Webhook
-
1
Go to Integrations → Webhooks in the sidebar.
-
2
Choose a Destination: Generic JSON, Slack, or Microsoft Teams.
-
3
Paste the endpoint URL for your chosen destination.
-
4
Select which events should trigger the webhook.
-
5
For Generic JSON webhooks, optionally add a secret to enable request signing.
-
6
Click Add Webhook. PingParrot will start posting to your URL immediately.
Auto-disable: If a webhook fails 10 times in a row, PingParrot automatically disables it to prevent noise. Re-enable it from the Webhooks page once the issue is resolved.
Which events can trigger a webhook?
Five events, subscribed to individually per webhook:
| Event | When it fires |
|---|---|
page.sent |
A page is created and delivery begins. |
page.acknowledged |
A recipient or admin acknowledges the page. |
page.cancelled |
A page is cancelled before acknowledgement. |
page.expired |
The page expires without being acknowledged. |
page.escalated |
An escalation rule fires and the page moves to the next step. |
Slack
PingParrot formats messages natively for Slack using Block Kit — no middleware or Zapier account required.
What it looks like
🚨 DB server is down
Critical
Active
Sarah Chen
PingParrot · New Page
Setup steps
-
1
In Slack, go to Apps → Manage Apps → Incoming Webhooks (or search "Incoming WebHooks" in the app directory).
-
2
Click Add to Slack, choose the channel you want pages to appear in, and click Authorise.
-
3
Copy the Webhook URL — it looks like
https://hooks.slack.com/services/T.../B.../... -
4
In PingParrot, go to Webhooks, select Slack as the destination, paste the URL, choose your events, and click Add Webhook.
Tip: Create one webhook per Slack channel. For example, route page.sent to #alerts and page.acknowledged to #on-call.
Microsoft Teams
PingParrot posts MessageCards directly to Teams channels — no Power Automate or third-party account needed.
What it looks like
🚨 DB server is down
PingParrot · New Page
Critical
Active
Sarah Chen
Setup steps
-
1
In Microsoft Teams, open the channel you want to receive alerts in.
-
2
Click the ••• (More options) next to the channel name → Connectors.
-
3
Find Incoming Webhook, click Configure, give it a name like "PingParrot", and click Create.
-
4
Copy the Webhook URL — it looks like
https://xxx.webhook.office.com/webhookb2/... -
5
In PingParrot, go to Webhooks, select Teams as the destination, paste the URL, choose your events, and click Add Webhook.
Zapier
Use Zapier to connect PingParrot pages to thousands of apps — PagerDuty, Jira, email, SMS, Discord, and more.
-
1
In Zapier, click Create Zap → Trigger → Webhooks by Zapier → Catch Hook.
-
2
Copy the Custom Webhook URL Zapier gives you.
-
3
In PingParrot, add a Generic JSON webhook with that URL and select your events.
-
4
Send a test page in PingParrot — Zapier will capture the payload so you can map fields.
-
5
Add your Action step (e.g. "Send a Slack message", "Create Jira issue", "Send SMS via Twilio").
-
6
Map the fields:
page.subject,page.priority,page.sender.name, etc. -
7
Turn on your Zap.
Make (formerly Integromat)
Make is a powerful automation platform with a generous free tier — great for complex multi-step workflows.
-
1
In Make, create a new Scenario and add a Webhooks → Custom webhook module as the trigger.
-
2
Click Add to create the webhook and copy the URL.
-
3
In PingParrot, add a Generic JSON webhook with that URL.
-
4
Send a test page — Make will detect the data structure automatically.
-
5
Add modules for your destination: Microsoft Teams → Create a message, Slack → Create a message, or any other app.
-
6
Map
page.subject,page.priority,event, and other fields to your action. -
7
Activate the scenario.
Discord
Discord supports incoming webhooks natively, but expects its own JSON format. Use Zapier or Make as a bridge, or a small custom relay.
Via Zapier / Make (recommended)
Follow the Zapier or Make steps above, then choose Discord → Send a channel message as the action. Map page.subject and page.priority into the message body.
Via a small relay (advanced)
If you have a server, a small script can receive PingParrot's Generic JSON payload and repost it to Discord's webhook URL:
// Express.js example
app.post('/relay', (req, res) => {
const { event, page } = req.body;
fetch(DISCORD_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
content: `${page.priority === 'critical' ? '🚨' : '🔔'} **${page.subject}** — ${event}`
})
});
res.sendStatus(200);
});
Custom / Generic
Use Generic JSON to integrate with any HTTP endpoint — your own backend, n8n, Pipedream, or anything else that can receive a POST request.
n8n: Add a Webhook trigger node, copy the URL into PingParrot as a Generic JSON webhook, then connect it to any n8n action (Teams, Slack, email, database, etc.).
Generic JSON Payload Reference
All Generic JSON webhooks send the same structure. Fields that don't apply to the event will be null.
{
"event": "page.acknowledged", // page.sent | acknowledged | cancelled | expired | escalated
"triggered_at": "2026-07-06T12:00:00+00:00",
"page": {
"id": 42,
"subject": "DB server is down",
"message": "All services affected. Investigating now.",
"priority": "critical", // low | normal | high | critical
"status": "acknowledged",
"delivery_mode": "single", // single | group | broadcast | oncall
"created_at": "2026-07-06T11:58:00+00:00",
"sender": {
"name": "Sarah Chen",
"email": "[email protected]"
},
"acknowledged_by": { // null unless event is page.acknowledged
"name": "James Nguyen",
"email": "[email protected]"
},
"cancelled_by": null // null unless event is page.cancelled
}
}
Field reference
| Field | Type | Description |
|---|---|---|
event |
string | The event name that triggered this webhook. |
triggered_at |
ISO 8601 | UTC timestamp of when the event occurred. |
page.id |
integer | Unique identifier for the page. |
page.subject |
string | The subject/title of the page. |
page.message |
string|null | The page body text, if one was included. |
page.priority |
string | low, normal, high, or critical. |
page.status |
string | Current status: active, acknowledged, cancelled, or expired. |
page.delivery_mode |
string | How the page was sent: single, group, broadcast, or oncall. |
page.sender |
object|null | Name and email of who sent the page. |
page.acknowledged_by |
object|null | Populated on page.acknowledged events. |
page.cancelled_by |
object|null | Populated on page.cancelled events. |
How do I verify a webhook actually came from PingParrot?
When a secret is set on a Generic JSON webhook, every request includes an X-PingParrot-Signature header. The value is sha256=<hmac> where the HMAC is computed over the raw request body using your secret as the key, per the HMAC standard (RFC 2104).
Verify it server-side to ensure the request came from PingParrot and wasn't tampered with:
Node.js
const crypto = require('crypto');
function verifySignature(body, secret, signature) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
// In your Express route:
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-pingparrot-signature'];
if (!verifySignature(req.body, process.env.WEBHOOK_SECRET, sig)) {
return res.status(401).send('Invalid signature');
}
const payload = JSON.parse(req.body);
// handle payload...
res.sendStatus(200);
});
PHP
$body = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_PINGPARROT_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $body, $_ENV['WEBHOOK_SECRET']);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit('Invalid signature');
}
$payload = json_decode($body, true);
// handle $payload...
Python
import hmac, hashlib
def verify_signature(body: bytes, secret: str, signature: str) -> bool:
expected = 'sha256=' + hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
# In your Flask route:
@app.route('/webhook', methods=['POST'])
def webhook():
sig = request.headers.get('X-PingParrot-Signature', '')
if not verify_signature(request.data, WEBHOOK_SECRET, sig):
abort(401)
payload = request.json
# handle payload...
Always use a timing-safe comparison (crypto.timingSafeEqual, hash_equals, hmac.compare_digest) rather than === to prevent timing attacks.
Need help?
Contact us and we'll help you get set up.