activecampaign/postmark-skills/postmark-webhooks

Claude Code and agent skill by activecampaign/postmark-skills

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Use when setting up Postmark outbound webhooks for tracking email delivery, bounces, opens, clicks, spam complaints, or subscription changes — includes webhook configuration, endpoint verification, per-webhook statistics, retry behavior, payload handling, and security.

Install

npx vskill@latest install activecampaign/postmark-skills/postmark-webhooks

SKILL.md

Postmark Webhooks

Overview

Postmark webhooks deliver real-time event data to your endpoint via HTTP POST. Use webhooks to track what happens after you send an email.

This skill covers outbound webhooks — the six event types below. Inbound email webhooks run on separate infrastructure with different retry behavior; see postmark-inbound for those.

EventTriggerCommon Use
DeliveryEmail accepted by recipient serverConfirm delivery, update status
BounceEmail rejected by recipient serverClean lists, alert support
SpamComplaintRecipient marked as spamRemove from lists, investigate
OpenRecipient opened email (tracking pixel)Engagement analytics
ClickRecipient clicked a tracked linkEngagement analytics, conversion tracking
SubscriptionChangeRecipient unsubscribedUpdate preferences, comply with regulations

Quick Start

  1. Create a webhook via API or Postmark dashboard (Server → Webhooks)
  2. Set your endpoint URL — must accept HTTP POST and return 200
  3. Select event triggers — choose which events to receive
  4. Pass verification — Postmark tests your endpoint for every enabled event type on create and edit; each must return HTTP 200 before the webhook is saved as verified
  5. Handle payloads — parse the JSON body for each event type
  6. Respond with 200 — acknowledge receipt immediately, then process

Webhook API

Endpoints
EndpointMethodDescription
/webhooksGETList all webhooks for a message stream
/webhooks/{webhookid}GETGet a specific webhook
/webhooksPOSTCreate a webhook
/webhooks/{webhookid}PUTUpdate a webhook
/webhooks/{webhookid}DELETEDelete a webhook
/webhooks/{Id}/verifyPOSTRun endpoint verification on demand
/webhooks/{Id}/statisticsGETDelivery statistics for one webhook (rolling 24 hours)
Create a Webhook
javascript
const postmark = require('postmark');
const client = new postmark.ServerClient(process.env.POSTMARK_SERVER_TOKEN);
const webhook = await client.createWebhook({
Url: 'https://yourdomain.com/webhooks/postmark',
MessageStream: 'outbound',
Verify: true, // default — test every enabled trigger before saving
HttpAuth: {
Username: 'webhook-user',
Password: 'webhook-secret'
},
HttpHeaders: [
{ Name: 'X-Custom-Header', Value: 'my-value' }
],
Triggers: {
Open: { Enabled: true, PostFirstOpenOnly: false },
Click: { Enabled: true },
Delivery: { Enabled: true },
Bounce: { Enabled: true, IncludeContent: true },
SpamComplaint: { Enabled: true, IncludeContent: true },
SubscriptionChange: { Enabled: true }
}
});
console.log('Webhook created:', webhook.ID);
cURL
bash
curl "https://api.postmarkapp.com/webhooks" \
-X POST \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "X-Postmark-Server-Token: $POSTMARK_SERVER_TOKEN" \
-d '{
"Url": "https://yourdomain.com/webhooks/postmark",
"MessageStream": "outbound",
"Verify": true,
"Triggers": {
"Open": { "Enabled": true, "PostFirstOpenOnly": false },
"Click": { "Enabled": true },
"Delivery": { "Enabled": true },
"Bounce": { "Enabled": true, "IncludeContent": true },
"SpamComplaint": { "Enabled": true, "IncludeContent": true },
"SubscriptionChange": { "Enabled": true }
}
}'
Trigger Options
TriggerOptions
OpenEnabled, PostFirstOpenOnly (true = only first open per recipient)
ClickEnabled
DeliveryEnabled
BounceEnabled, IncludeContent (include original email content)
SpamComplaintEnabled, IncludeContent
SubscriptionChangeEnabled

Endpoint Verification

When a webhook is created or edited, Postmark tests the endpoint for every enabled event type. Each test must return HTTP 200 before the webhook is saved as verified.

The Verify field
FieldTypeDefaultBehavior
Verifybooleantruetrue — test every enabled trigger before saving. false — save the webhook without testing; it stays unverified and receives no events until it passes verification later.

If verification fails on create or edit, the save is rejected with HTTP 422 and error code 1364. Nothing is saved on create; on edit the existing webhook is left unchanged.

Verify on demand

POST /webhooks/{Id}/verify runs the check on demand.

A 200 response means the check RAN — not that it passed. Read the Success field to determine the result: Success can be false inside an HTTP 200 response. Never treat the status code as "verified."

The response body reports per-trigger results — Id, Url, Success, Results[] (each with TriggerType, Success, StatusCode, Message), and a summary Message such as "4/5 triggers verified successfully". Top-level Success is true only if every enabled trigger returned 200.

See references/webhook-setup.md for the full response shape and field reference.

Persistent failure pauses one event type

If an event type keeps failing across many events, Postmark marks that event type unverified and pauses delivery for it until the endpoint is fixed and re-verified.

This is tracked per event type — Bounce failing does not pause Delivery or Open. Existing webhooks start as verified at launch cutover.

Retries and Failure Handling

Retries apply to outbound webhooks. Inbound email webhooks use a different, longer schedule — see postmark-inbound. Do not apply one schedule to the other.

Which failures are retried
ResponseClassificationBehavior
5xxTemporaryRetried
408 Request TimeoutTemporaryRetried
429 Too Many RequestsTemporaryRetried
Network timeoutTemporaryRetried
Every other 4xx (400, 401, 403, 404, 405, 410, 422)PermanentDropped on the first attempt — no retries
3xxTemporaryRedirects are followed by default, so Postmark classifies the final status. A bare 3xx is retried, not dropped
Retry schedule

When a failure is retryable, a webhook gets up to 9 retries over approximately 72 minutes. Delivery runs in tiers, so a failing webhook works through two rungs in order:

RungRetriesIntervalsElapsed
Standard31 min, 5 min, 15 min21 min
Backoff (entered after standard is exhausted)61 min, 5 min, 10 min, 10 min, 10 min, 15 min51 min
Total9~72 min

After the 9th retry the event is dropped permanently. The gap does not widen monotonically — it resets to 1 minute when the backoff rung starts.

The same 9-retry ladder applies to every outbound event type and cannot be customized per webhook or per event type.

Each attempt carries an `X-PM-Retries-Remaining` header. It starts at 9 on the first failure and counts down across both rungs — a 9 early in an incident is expected, not a misconfiguration.

Webhook Statistics

GET /webhooks/{Id}/statistics returns delivery statistics for one webhook, keyed by ID. There is no all-webhooks or per-stream variant and no status filter. The window is a fixed rolling 24 hours — not filterable in v1.

The response contains:

FieldContents
WebhookId, UrlWhich webhook the statistics describe
StatusesVerification status per event type (verified / unverified)
TimeRangeStartTime, EndTime, Hours — always a rolling 24 hours
MetricsTop-level aggregate: TotalRequests, SuccessCount, FailureCount, RetryCount, SuccessRate, SlowCount, VerySlowCount, AverageTerminalResponseTimeMs, AverageRetryResponseTimeMs
MetricsByTriggerThe same metric fields, broken out per event type
  • AverageRetryResponseTimeMs is null when a trigger had no retries.
  • The same metric fields appear in both Metrics and each MetricsByTrigger entry.

Use Statuses to spot a paused event type and per-trigger SuccessRate to find which one is failing.

See references/webhook-setup.md for the full response shape and field reference.

Webhook Payloads

All payloads include RecordType, MessageID, MessageStream, and Metadata (from the original send). Use RecordType to route events:

javascript
app.post('/webhooks/postmark', (req, res) => {
res.sendStatus(200); // respond immediately
const event = req.body;
switch (event.RecordType) {
case 'Delivery': handleDelivery(event); break;
case 'Bounce': handleBounce(event); break;
case 'SpamComplaint': handleSpamComplaint(event); break;
case 'Open': handleOpen(event); break;
case 'Click': handleClick(event); break;
case 'SubscriptionChange': handleSubscriptionChange(event); break;
}
});
Bounce Types
TypeCodeAction
HardBounce1Permanent — remove address from all lists
SoftBounce4096Temporary — Postmark retries; monitor
Transient2Temporary — retry may succeed
SpamNotification512Marked as spam at recipient's server
Blocked16Blocked by recipient server
DMARCPolicy100000Rejected due to DMARC policy

See references/payload-examples.md for full JSON payloads for all 6 event types.

See references/handler-examples.md for complete Node.js and Python implementations, async processing, deduplication, and metadata correlation.

Idempotency

A retry can redeliver an event your endpoint already processed — for example if it processed the event but responded too slowly. Handlers must be idempotent.

Deduplicate on the retry-stable `X-PM-Webhook-Trace-Id` header, falling back to MessageID when the header is absent. Respond 200 first, then process.

See references/handler-examples.md for Node.js and Python deduplication implementations.

Security

Postmark does not sign webhook requests — there is no HMAC signature or signing header to verify. Do not write or trust signature-verification logic in a webhook handler.

Protect your endpoint with HTTP Basic Auth (credentials in the webhook URL, or the HttpAuth field), IP allowlisting, and payload-shape validation.

See references/security.md for full implementation examples.

Bounce Management

Use the Bounces API and Suppression Management API alongside webhooks for comprehensive bounce handling.

See references/bounce-management.md for the Bounces API, suppression management, and bounce rate thresholds.

Webhook Management

See references/webhook-setup.md for list, update, delete, verification, statistics, and retry schedule details.

Common Mistakes

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
46
Repository forks
5
7-day trend score
17

Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 18 Mar 2026, 7 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/activecampaign/postmark-skills/postmark-webhooks/badge)](https://verified-skill.com/skills/activecampaign/postmark-skills/postmark-webhooks)

Something wrong with this skill or its page? Tell us.