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.

How it works

In three steps:

  1. Add an endpoint, an HTTPS URL you control, and pick the events it should receive.
  2. Receive signed events, when a subscribed event happens, we POST a JSON payload to your URL, signed with a per-endpoint secret (whsec_…).
  3. Verify and respond, check the signature, do your work, and return a 2xx status. Non-2xx responses are retried.

Good to know:

  • No polling needed. Each event embeds the full /v1/track response, 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 2xx as 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.

  • 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.


POSTyour endpoint

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/track response for this impact, project location, agents, certificate, completion status, video, and more.

Example event

POST
your endpoint
{
  "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 of t + . + the raw request body, keyed with your endpoint's signing secret (whsec_…).

To verify a request:

  1. Read your endpoint's signing secret from the dashboard.
  2. Take the raw request body (do not re-serialize the parsed JSON).
  3. Compute HMAC_SHA256(secret, "{t}.{rawBody}") and hex-encode it.
  4. Compare it to v1 using a constant-time comparison.

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 2xx status. Respond as soon as you have persisted the event, do slow work asynchronously.
  • 429 and 5xx responses (and network errors) are retried with a short backoff.
  • Other 4xx responses 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-Id changes on every attempt; use it for tracing individual deliveries, not for deduplication.)
  • Return 2xx fast 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.track object over calling /v1/track again, it already contains the latest lifecycle state.