Webhooks
Get Ardent events pushed to your server, signed and verifiable.
Instead of polling, subscribe to events and Ardent will POST them to your URL as they happen. Create and manage webhooks in your developer dashboard.
Events
| Event | When | Payload |
|---|---|---|
petition.signed | a petition gets a new signature | petition_slug, signatures_count |
campaign.published | a campaign goes live | slug, title, category, goal_amount, target_country |
campaign.updated | a live campaign's details change | slug, title |
campaign.goal_reached | a campaign hits its goal | slug, title, goal_amount, raised_amount |
event.published | an event is published | slug, title, start_date, is_online, is_paid |
event.ticket_sold | a paid ticket is confirmed | event_slug, quantity |
Payloads contain public fields only — never donor, signer, organizer, or attendee personal data.
Delivery
Each delivery is a POST with a JSON body and these headers:
Content-Type: application/json
X-Ardent-Event: petition.signed
X-Ardent-Signature: t=1718900000,v1=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08Respond with any 2xx to acknowledge. Non-2xx (or a timeout) is retried with exponential
backoff for up to 8 attempts, after which the delivery is dead-lettered — you can replay
it from the dashboard. You can also send a test event to any endpoint.
Verifying the signature
v1 is the hex HMAC-SHA256 of <t>.<raw-request-body>, keyed by your subscription's signing
secret (shown once when you create or rotate the webhook). Always verify before trusting a
payload, and reject timestamps outside a few minutes to prevent replays.
// Node — use the RAW request body, not a re-serialized object.
import crypto from 'node:crypto'
function verify(secret, signatureHeader, rawBody) {
const parts = Object.fromEntries(signatureHeader.split(',').map((p) => p.split('=')))
const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex')
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300
return ok && fresh
}<?php
function ardent_verify(string $secret, string $header, string $rawBody): bool {
parse_str(str_replace(',', '&', $header), $p);
$expected = hash_hmac('sha256', $p['t'] . '.' . $rawBody, $secret);
return hash_equals($expected, $p['v1'] ?? '') && abs(time() - (int) $p['t']) < 300;
}Security notes
- Only HTTPS endpoints are accepted, and Ardent will not deliver to private, loopback, or link-local addresses.
- Treat the signing secret like a password. If it leaks, rotate it from the dashboard.
- Delivery is at-least-once — handle the occasional duplicate idempotently.
Last updated: 28 June 2026
