Skip to content

Webhook events

Reference
For:
Developers
Needs permission:
webhooks:manage
In the app:
Settings, then Webhooks

This page lists the webhook event types an endpoint in Settings, then Webhooks can subscribe to, and the envelope every delivery uses. Receive webhooks shows how to create an endpoint and verify signatures.

Every delivery is a POST with a JSON body of five fields:

Field Value
id The event ID (UUID). Every endpoint that receives the event gets the same ID; use it to skip duplicates.
type The event type, for example email.bounced.
created_at When the event happened, in UTC (RFC 3339).
workspace The workspace slug.
data The event’s payload. Its shape depends on the type; six events that integrations use have a sample payload in their row below.

Testing an endpoint sends a webhook.test event:

webhook.test
{
"id": "01934f6e-8b2a-7c3d-9e4f-5a6b7c8d9e0f",
"type": "webhook.test",
"created_at": "2026-10-22T09:30:00.482913Z",
"workspace": "acme-newsletter",
"data": {
"message": "Test event from ReputeCast. Verify the signature, then answer 2xx."
}
}
Header Value
Content-Type application/json
User-Agent ReputeCast-Webhooks/<version>
X-ReputeCast-Event The event type.
X-ReputeCast-Delivery The delivery ID: one per endpoint and event, unchanged across retries.
X-ReputeCast-Timestamp Unix time in seconds when this attempt was made.
X-ReputeCast-Signature Lowercase hex HMAC-SHA256 of <timestamp>.<raw body>, keyed with the whole whsec_… secret. Reject timestamps more than 5 minutes off.
  • A 2xx answer within 10 seconds counts as delivered. Any other status, a timeout or a connection error is retried.
  • An event is tried up to 8 times, waiting 1, 2, 4, 8, 30, 90 and 225 minutes between attempts (about six hours in total). After the eighth failure it moves to the dead-letter queue, where you can replay it.
  • A paused endpoint keeps queuing events; when you resume it, the queued events are delivered in order.
  • The webhook.test event is tried once and never retried.

An endpoint subscribes to 1–40 entries. Each entry is an exact event type (email.bounced), every event of a group (email.*, matching the part before the first dot) or every event (*).

EventDescription
contact.subscribedA contact joined a list (after double opt-in when it's on).
Sample payload
{
"company": "",
"contact_id": "0192a4c1-7d4e-7c1e-9a40-3f1b2c8d5e01",
"country": "MX",
"email": "lucia.fernandez@example.com",
"fields": {
"plan": "Team"
},
"first_name": "Lucía",
"language": "es-MX",
"last_name": "Fernández",
"list_id": "0192a4c1-0000-7000-8000-00000000b3a1",
"list_name": "Weekly Digest",
"lists": [
{
"id": "0192a4c1-0000-7000-8000-00000000b3a1",
"name": "Weekly Digest",
"status": "subscribed"
}
],
"source": "form",
"status": "active",
"tags": [
"beta-applicant"
]
}
contact.unsubscribedA contact left a list or unsubscribed from everything.
Sample payload
{
"company": "",
"contact_id": "0192a4c1-7d4e-7c1e-9a40-3f1b2c8d5e01",
"country": "MX",
"email": "lucia.fernandez@example.com",
"fields": {
"plan": "Team"
},
"first_name": "Lucía",
"language": "es-MX",
"last_name": "Fernández",
"list_id": "0192a4c1-0000-7000-8000-00000000b3a1",
"list_name": "Weekly Digest",
"lists": [
{
"id": "0192a4c1-0000-7000-8000-00000000b3a1",
"name": "Weekly Digest",
"status": "subscribed"
}
],
"source": "form",
"status": "active",
"tags": [
"beta-applicant"
]
}
contact.updatedFields, tags or preferences changed.
Sample payload
{
"changed": [
"language",
"tags"
],
"company": "",
"contact_id": "0192a4c1-7d4e-7c1e-9a40-3f1b2c8d5e01",
"country": "MX",
"email": "lucia.fernandez@example.com",
"fields": {
"plan": "Team"
},
"first_name": "Lucía",
"language": "es-MX",
"last_name": "Fernández",
"lists": [
{
"id": "0192a4c1-0000-7000-8000-00000000b3a1",
"name": "Weekly Digest",
"status": "subscribed"
}
],
"status": "active",
"tags": [
"beta-applicant"
]
}
contact.erasedPersonal data was erased.
EventDescription
email.sentA sending provider accepted a message.
email.deliveredThe mailbox provider accepted the message.
email.deferredDelivery was delayed and will be retried.
email.bouncedHard or soft bounce, with the provider's original code.
email.complainedThe recipient marked the message as spam.
email.openedA human open (machine opens carry machine: true).
email.clickedA tracked link was clicked.
EventDescription
campaign.scheduledA campaign was scheduled.
campaign.startedA send started.
campaign.pausedA send paused, by a person or automatically.
campaign.resumedA paused send resumed.
campaign.completedA send finished.
Sample payload
{
"campaign_id": "0192a4c1-0000-7000-8000-0000000c0142",
"completed_at": "2026-10-14T13:41:00Z",
"delivered": 31352,
"name": "Weekly Digest #142",
"sent": 31604,
"started_at": "2026-10-14T13:00:00Z",
"status": "sent",
"subject": "Three launches, one deprecation, and the roadmap for Q4"
}
EventDescription
approval.requestedA send needs approval.
approval.decidedA send was approved, rejected or sent back.
EventDescription
provider.downA sending provider is failing or unreachable.
provider.recoveredA sending provider is healthy again.
EventDescription
automation.publishedA flow was published.
EventDescription
message.sentA sending provider accepted a transactional message.
message.deliveredThe mailbox provider accepted the message.
message.deferredDelivery was delayed and will be retried.
message.bouncedHard or soft bounce, with the provider's original code.
Sample payload
{
"message_id": "msg_01J9ZK7Q2X8M",
"metadata": {
"user_id": "u_8812"
},
"provider": "postmark",
"reason": "550 5.1.1 The email account that you tried to reach does not exist",
"subtype": "hard",
"tags": [
"auth"
],
"template": "password-reset",
"to": "sam.rivera@outlook.com"
}
message.complainedThe recipient marked the message as spam.
message.rejectedStopped before sending: suppressed or opted out of its category.
message.failedEvery provider in the route failed after the retries.
message.openedThe message was opened (templates with open tracking).
message.clickedA tracked link was clicked (templates with click tracking).
EventDescription
form.submittedA signup was confirmed (at once for single opt-in), with the form and the answers.
Sample payload
{
"answers": {
"email": "lucia.fernandez@example.com",
"first_name": "Lucía"
},
"confirmed_at": "2026-10-03T14:05:40Z",
"consent_text": "I agree to receive emails from Acme. Unsubscribe any time with one click.",
"contact_id": "0192a4c1-7d4e-7c1e-9a40-3f1b2c8d5e01",
"double_opt_in": true,
"email": "lucia.fernandez@example.com",
"form_id": "frm_8c2e1a",
"form_name": "Newsletter signup",
"language": "es",
"lists": [
"Weekly Digest"
],
"submitted_at": "2026-10-03T14:02:11Z"
}
EventDescription
referral.countedA referral counted: its subscriber confirmed and stayed through the hold.
referral.heldA referral was held for review by a suspicion rule.
referral.not_countedA referral didn't count, with the reason.
referral.milestone_reachedA referrer reached a milestone.
referral.reward_sentA reward went to a referrer (a download, a code, a claim link).
referral.reward_failedA webhook reward failed after 6 hours of retries.
EventDescription
subscription.trial_startedA paid subscription started with a free trial.
subscription.startedA paid subscription started and its first payment was made.
subscription.renewedA subscription renewed and its payment went through.
subscription.payment_failedA payment failed; the provider retries.
subscription.cancel_scheduledA subscription will end at the end of its period.
subscription.endedPaid access ended: canceled, unpaid, or a trial that ended without payment.
subscription.changedA paid member switched tier or between monthly and yearly.
access.complimentary_grantedA team member gave complimentary access.
access.complimentary_endedComplimentary access ended or was removed.
gift.purchasedA reader bought a gift subscription.
gift.redeemedA gift was redeemed by its recipient.
conversion.recordedAn upgrade or a conversion was credited to an email.
EventDescription
order.paidA buyer paid for a digital product; the delivery email is on its way.
order.refundedAn order was refunded in Stripe, in full or in part.
order.disputedA buyer disputed an order's payment with their bank.
EventDescription
push.clickedA reader clicked a notification or one of its buttons (once per notification).
EventDescription
sms.deliveredA carrier delivered a text.
sms.failedA text failed, with the reason and the provider's own code.
sms.clickedA person clicked a text's short link (link previews carry machine: true).
sms.opted_outA number texted STOP: SMS consent withdrawn on every list.
sms.opted_inA number texted START: the consents STOP removed came back.
sms.repliedA reply that isn't a keyword, with its text.
EventDescription
post.publishedA post went live on the website, by a person, at its time or with its campaign's send.
post.unpublishedA post was taken off the website or deleted.
EventDescription
whatsapp.deliveredMeta delivered a WhatsApp message.
whatsapp.readA person read a WhatsApp message (only people who share read receipts).
whatsapp.failedA WhatsApp message failed, with the reason and Meta's code.
whatsapp.repliedA person wrote or tapped a quick reply: the 24-hour window opens.
whatsapp.opted_outA number wrote an opt-out keyword: WhatsApp consent withdrawn on every list.
whatsapp.opted_inA number wrote an opt-in keyword: the consents the opt-out removed came back.
whatsapp.template_statusMeta approved, rejected, paused or disabled a template.