Ardent AfricaDocs
Developers

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

EventWhenPayload
petition.signeda petition gets a new signaturepetition_slug, signatures_count
campaign.publisheda campaign goes liveslug, title, category, goal_amount, target_country
campaign.updateda live campaign's details changeslug, title
campaign.goal_reacheda campaign hits its goalslug, title, goal_amount, raised_amount
event.publishedan event is publishedslug, title, start_date, is_online, is_paid
event.ticket_solda paid ticket is confirmedevent_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=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08

Respond 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

On this page