Webhooks
Webhooks let your software react the moment something changes in Argenta. You register an endpoint (a URL on your server), choose which events it should receive, and Argenta sends that URL a signed HTTPS request the instant one of those events happens. One endpoint can subscribe to as many events as you like, or to all of them.
Set up an endpoint
In Argenta, go to Subscription Settings → Webhooks → Add Endpoint and provide:
- Endpoint URL — an
https://address on your server that will receive the events. - Events — pick the specific events you want, or turn on Receive all events (this includes any new event types Argenta adds later).
- Signing secret — generated for you. Copy it and store it securely; you use it to verify that each request really came from Argenta. You can rotate it at any time.
You can pause an endpoint (Active off) to stop delivery without deleting it.
Events
Each event has a type in resource.action form. Today's catalog:
Constituents
| Event | When it fires |
|---|---|
constituent.created | A new constituent is added. |
constituent.updated | An existing constituent's details change. |
constituent.deleted | A constituent is deleted. |
Volunteers
| Event | When it fires |
|---|---|
volunteer.created | A constituent is designated as a volunteer. |
volunteer.updated | A volunteer's details change. |
volunteer.deleted | A volunteer is removed (the constituent record remains). |
Volunteer tasks
| Event | When it fires |
|---|---|
volunteer_task.created | A new volunteer task is created. |
volunteer_task.updated | A volunteer task's details change. |
volunteer_task.deleted | A volunteer task is deleted. |
volunteer_task.signed_up | A volunteer is assigned to (signs up for) a task. A sign-up also fires volunteer_task.updated, so subscribe to whichever fits your integration. |
volunteer_task.canceled | A volunteer cancels a task they signed up for (through the cancel form or volunteer portal). |
volunteer_task.unassigned | An Argenta staff user unassigns the volunteer from a task. |
Donations
| Event | When it fires |
|---|---|
donation.created | A new money donation is recorded, from any source: online form, Stripe, PayPal, import, or hand-entered. |
donation.updated | An existing donation's details change. |
donation.deleted | A donation is deleted. |
donation.refunded | A refund is issued against a donation. The payload carries the refund amount and the refunded donation's id. |
donation.receipted | A receipt is issued for a gift (the receipt flag flips on). |
donation.thanked | A thank-you is sent for a gift (the thank-you flag flips on). |
donation.recurring_started | A donor sets up a recurring (e.g. monthly) gift. |
donation.recurring_canceled | A recurring gift schedule is canceled. |
More event types (events, memberships, and others) will be added over time. If your endpoint is set to Receive all events, it automatically starts receiving new types as they ship — no change needed on your side.
The request we send
Every event is an HTTP POST to your endpoint URL with a JSON body and these headers:
| Header | Meaning |
|---|---|
X-Argenta-Event | The event type, e.g. constituent.created. |
X-Argenta-Delivery | A unique id for this delivery attempt. |
X-Argenta-Signature | The signature to verify the request (see below). |
Content-Type | application/json |
The JSON body is an envelope:
{
"id": "evt_415109_1783299902",
"type": "constituent.created",
"created": 1783299902,
"team": 6,
"data": {
"PkConstituent": 415109,
"FkTeam": 6,
"ConstituentName": "Jane Smith",
"FirstName": "Jane",
"LastName": "Smith",
"Email1": "[email protected]",
"Phone1": "5551234567",
"DateCreated": "2026-07-05T21:04:31.263"
}
}id— unique id for the event.type— the event type (matchesX-Argenta-Event).created— Unix timestamp (seconds, UTC) when the event was generated.team— the Argenta team the record belongs to.data— the record for that event. Constituent events carry the constituent's fields; volunteer and volunteer-task events carry that record's fields (a task event includes its title, start/end times, and the assigned volunteer); donation events carry the amount, gift date, designated purpose, and the linked donor. Update events addDateLastModified, delete events addDeletedUtc. The volunteer-task person events (volunteer_task.signed_up,volunteer_task.canceled, andvolunteer_task.unassigned) instead nest theirdataintoconstituent,task, andtaskGroupobjects, plus a top-levelEventUtctimestamp (see below).
Task person-event payload (sign-up, cancel, unassign)
The volunteer_task.signed_up, volunteer_task.canceled, and
volunteer_task.unassigned events share one richer, nested data object: the
constituent (the volunteer who signed up, or the one who was removed for a cancel or
unassign), the task, and the taskGroup it belongs to, plus a top-level
EventUtc timestamp. All three use the identical shape; the type field tells
them apart, so a single handler can process every one. PkTask is a number;
PkConstituent and PkTaskGroup are strings.
{
"id": "evt_202156_1784222109",
"type": "volunteer_task.signed_up",
"created": 1784222109,
"team": 6,
"data": {
"constituent": {
"PkConstituent": "415109",
"FirstName": "Jane",
"LastName": "Smith",
"Email1": "[email protected]",
"Phone1": "5551234567"
},
"task": {
"PkTask": 202156,
"TaskStartDateTime": "2026-11-30T13:00:00",
"TaskEndDateTime": "2026-11-30T16:00:00",
"VolunteerTaskTitle": "HQ Assistant #1",
"VolunteerTaskDescription": ""
},
"taskGroup": {
"PkTaskGroup": "94058",
"TaskGroupTitle": "HQ Staffing - Fall 2026",
"GroupLocationName": "Main Office",
"DistrictName": "District 5",
"PrecinctName": "Precinct 12"
},
"EventUtc": "2026-07-16T17:15:05.197"
}
}Verify the signature
The X-Argenta-Signature header looks like:
t=1783299902,v1=f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8t— the Unix timestamp (seconds) when we signed the request.v1— an HMAC-SHA256, hex-encoded, of the string<t>.<body>using your endpoint's signing secret as the key.<body>is the exact raw request body.
To verify: compute the HMAC yourself and confirm it matches v1. Reject the request if it
doesn't, or if t is too old for your tolerance (guards against replay).
const crypto = require('crypto');
function verify(rawBody, signatureHeader, secret) {
const m = /t=(\d+),v1=([0-9a-f]+)/.exec(signatureHeader || '');
if (!m) return false;
const [, t, v1] = m;
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}
using System.Security.Cryptography;
using System.Text;
bool Verify(string rawBody, string signatureHeader, string secret)
{
var m = System.Text.RegularExpressions.Regex.Match(signatureHeader ?? "", @"t=(\d+),v1=([0-9a-f]+)");
if (!m.Success) return false;
var t = m.Groups[1].Value;
var v1 = m.Groups[2].Value;
using var h = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var hash = h.ComputeHash(Encoding.UTF8.GetBytes($"{t}.{rawBody}"));
var expected = Convert.ToHexString(hash).ToLowerInvariant();
return CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(v1));
}Responding, retries, and duplicates
- Respond quickly with a 2xx. Any
2xxstatus tells us the delivery succeeded. Do your slow work after you respond (queue it), not before. - Retries. If your endpoint returns a non-2xx or times out, we retry with exponential backoff (about 30s, then 1m, 2m, 4m… up to roughly an hour) for several attempts before giving up.
- At-least-once delivery. A webhook may occasionally be delivered more than once. Use the event
id(orX-Argenta-Delivery) to make your handler idempotent — safe to receive twice. - Order is not guaranteed. Don't assume events arrive in the exact order they happened; use the record's own timestamps if order matters.
Checklist
- Endpoint is
https://and returns2xxfast. - You verify
X-Argenta-Signatureon every request with your signing secret. - Your handler is idempotent on the event
id. - You store the signing secret securely and rotate it if it's ever exposed.