Webhooks
Webhooks let you receive impact updates in real time. Instead of polling the Track API, you register an endpoint once and 1ClickImpact pushes a signed JSON event to it every time an impact moves through its lifecycle, from initiation to the moment the donation is sent and the impact is completed.
Webhooks are available on the Basic and Pro plans. Create and manage them in your dashboard under Account → Webhooks. Each account can register up to 10 endpoints.
How it works
In three steps:
- Add an endpoint, an HTTPS URL you control, and pick the events it should receive.
- Receive signed events, when a subscribed event happens, we
POSTa JSON payload to your URL, signed with a per-endpoint secret (whsec_…). - Verify and respond, check the signature, do your work, and return a
2xxstatus. Non-2xxresponses are retried.
Good to know:
- No polling needed. Each event embeds the full
/v1/trackresponse, so the data you would have polled for is already in the payload. - Every attempt is logged. Inspect deliveries, and send test events, from the delivery history in your dashboard.
- Respond fast. Return
2xxas soon as you've stored the event, then do slower work asynchronously.
Event types
Subscribe an endpoint to any combination of the events below (or to all events). Each corresponds to a stage of the impact lifecycle also returned by the Track API.
A typical impact fires them in this order:
impact.initiated → impact.donation_available → impact.donation_sent → impact.completed
impact.cancelled can arrive instead if an impact is reversed. Not every impact emits every event, so handle each one on its own rather than assuming a fixed sequence.
- Name
impact.initiated- Type
- event
- Description
The impact was created and its lifecycle has started.
- Name
impact.donation_available- Type
- event
- Description
Funds for the impact became available to send to the project.
- Name
impact.donation_sent- Type
- event
- Description
Funds were transferred to the project partner.
- Name
impact.completed- Type
- event
- Description
The impact was fully completed and verified.
- Name
impact.cancelled- Type
- event
- Description
The impact was cancelled.
Event payload
Each delivery is a JSON object with a stable envelope. The data.track object mirrors the /v1/track response exactly (same snake_case fields), so anything you already parse from the Track API is available here.
- Name
id- Type
- string
- Description
Unique identifier for this event. Stable across retries of the same event, use it to deduplicate.
- Name
type- Type
- string
- Description
The event type, e.g.
impact.donation_sent. See Event types.
- Name
sent_at- Type
- string
- Description
UTC timestamp (ISO 8601) when 1ClickImpact generated the event.
- Name
data.tracking_id- Type
- string
- Description
The tracking ID of the impact, formatted as user_id-time_utc.
- Name
data.user_id- Type
- string
- Description
The user ID the impact belongs to (your organization's user or a customer).
- Name
data.org_id- Type
- string
- Description
The organization ID, when the impact is associated with one. Omitted otherwise.
- Name
data.occurred_at- Type
- string
- Description
UTC timestamp of the lifecycle change that triggered this event.
- Name
data.track- Type
- object
- Description
The full
/v1/trackresponse for this impact, project location, agents, certificate, completion status, video, and more.
Example event
{
"id": "U1234-2026-03-12T15:22:14.753Z#impact.donation_sent#2026-03-19T15:22:14.753Z",
"type": "impact.donation_sent",
"sent_at": "2026-03-19T15:27:14.753Z",
"data": {
"tracking_id": "U1234-2026-03-12T15:22:14.753Z",
"user_id": "U1234",
"org_id": "O5678",
"occurred_at": "2026-03-19T15:22:14.753Z",
"track": {
"tracking_id": "U1234-2026-03-12T15:22:14.753Z",
"impact_initiated": "2026-03-12T15:22:14.753Z",
"tree_planted": 5,
"donation_available": "2026-03-13T15:22:14.753Z",
"donation_sent": "2026-03-19T15:22:14.753Z",
"impact_completed": null,
"certificate": "https://1clickimpact.com/certificate/U1234-..."
}
}
}
Request headers
Content-Type: application/json
User-Agent: 1ClickImpact-Webhooks/1
X-1CI-Event-Type: impact.donation_sent
X-1CI-Delivery-Id: 3f9a…c21
X-1CI-Signature: t=1742398034,v1=5f8b…e0
Verifying signatures
Every request includes an X-1CI-Signature header so you can confirm it was sent by 1ClickImpact and was not tampered with. The header has two comma-separated fields:
t, the UNIX timestamp (seconds) when the event was signed.v1, the signature: a hex-encoded HMAC-SHA256 oft+.+ the raw request body, keyed with your endpoint's signing secret (whsec_…).
To verify a request:
- Read your endpoint's signing secret from the dashboard.
- Take the raw request body (do not re-serialize the parsed JSON).
- Compute
HMAC_SHA256(secret, "{t}.{rawBody}")and hex-encode it. - Compare it to
v1using a constant-time comparison.
Optionally reject events whose t is older than a few minutes to guard against replay. Always compare signatures with a timing-safe function.
Verify a webhook signature
const crypto = require('crypto')
// rawBody: the exact bytes you received (Buffer/string), not JSON.parse'd
function verifyWebhook(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(
signatureHeader.split(',').map((kv) => kv.split('=')),
)
const signed = `${parts.t}.${rawBody}`
const expected = crypto
.createHmac('sha256', secret)
.update(signed)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(parts.v1),
)
}
Delivery & retries
- A delivery is considered successful when your endpoint returns any
2xxstatus. Respond as soon as you have persisted the event, do slow work asynchronously. 429and5xxresponses (and network errors) are retried with a short backoff.- Other
4xxresponses are treated as a client misconfiguration and are not retried. - Every attempt (status code, attempt count, and payload) is stored in the delivery history in your dashboard, where you can also send test events.
Best practices
- Verify every request with the signature before trusting it.
- Deduplicate on the event
id, it stays the same across retries, so you can safely process each event once. (X-1CI-Delivery-Idchanges on every attempt; use it for tracing individual deliveries, not for deduplication.) - Return
2xxfast and process asynchronously to avoid retries. - Rotate the secret from the dashboard if it is ever exposed; deliveries are signed with the current secret.
- Prefer the event's
data.trackobject over calling/v1/trackagain, it already contains the latest lifecycle state.

