API-documentatie

Met de API van Giift koppel je je eigen systemen: je website, je boekhouding, of Zapier en Make. Je leest en wijzigt contacten, legt toestemming vast, beheert campagnes en krijgt via webhooks bericht als er iets verandert.

Aan de slag

  1. Maak een API-sleutel bij Account → API. Alleen de eigenaar en beheerders van een organisatie kunnen dat.
  2. Kies wat de sleutel mag: alles, alleen lezen, of alleen formulieren (voor Miix Donate in WordPress).
  3. Stuur de sleutel mee in de header Authorization: Bearer giift_sk_… bij elk verzoek naar https://api.giift.nl/v1.
Je eerste verzoek
curl https://api.giift.nl/v1/contacts?limit=10 \
  -H "Authorization: Bearer $GIIFT_API_KEY"

Een sleutel geeft toegang tot de gegevens van je donateurs. Bewaar hem op de server, nooit in de code van een webpagina of app, en trek hem in bij Account → API als hij is uitgelekt.

Sleutels en rechten

Elke sleutel hoort bij één organisatie en heeft een bereik:

BereikMag
fullAlles: lezen en wijzigen van contacten, campagnes en webhooks.
readAlleen lezen: contacten, tijdlijn, campagnes en webhooks ophalen.
checkoutAlleen wat een donatieformulier nodig heeft: formulieren, giften via een formulier en campagnetotalen. Voor Miix Donate in WordPress.

Alles wat je via de API met een contact doet (bekijken, wijzigen, verwijderen) komt in het logboek van de organisatie, onder de naam van de sleutel. Een verzoek dat het bereik niet toestaat krijgt 403 forbidden.

Afspraken

  • Verzoeken en antwoorden zijn JSON; stuur Content-Type: application/json mee.
  • Bedragen staan altijd in hele centen (goalCents: 500000 is € 5.000), in euro.
  • Datums en tijden zijn ISO 8601 met tijdzone, bijvoorbeeld 2026-12-31T23:59:00+01:00. Antwoorden zijn in UTC.
  • Een lijst geeft data, page, limit, totalPages en totalCount. Blader met ?page=2&limit=100 (maximaal 100).
  • Per sleutel zijn 120 verzoeken per minuut toegestaan. Daarboven krijg je 429 met een Retry-After-header in seconden.
  • Binnen /v1 komen er alleen dingen bij: nieuwe velden, endpoints en gebeurtenissen. Negeer velden die je niet kent.
Een lijst
{
  "data": [ { "id": 4821, "firstName": "Anna", "...": "..." } ],
  "page": 1,
  "limit": 50,
  "totalPages": 3,
  "totalCount": 128
}

Fouten

Een fout heeft altijd dezelfde vorm: een code om in je code op te reageren, een message voor mensen en soms details per veld.

422 invalid_request
{
  "error": {
    "code": "invalid_request",
    "message": "The contact is invalid. See details.",
    "details": [
      { "path": "email", "message": "invalid_email" },
      { "path": "birthday", "message": "invalid_date" }
    ]
  }
}
StatusCodeBetekenis
400/422invalid_requestHet verzoek klopt niet; zie details.
401unauthorizedGeen of een ongeldige sleutel.
403forbiddenHet bereik van de sleutel staat dit niet toe.
403account_blockedDe organisatie is geblokkeerd; neem contact met ons op.
404not_foundBestaat niet, of hoort bij een andere organisatie.
409conflictBotst met iets dat er al is, zoals een e-mailadres.
429rate_limitedTe veel verzoeken; wacht Retry-After seconden.
500server_errorEr ging bij ons iets mis. Probeer het later nog eens.

Contacten

Een contact is een persoon of een organisatie. Een persoon kan contactpersoon zijn van een organisatie-contact (organisationContactId, met jobTitle).

Een contact
{
  "data": {
    "id": 4821,
    "kind": "person",
    "firstName": "Anna",
    "lastName": "de Vries",
    "organisationName": null,
    "jobTitle": null,
    "email": "anna@example.nl",
    "phone": "06 12345678",
    "birthday": "1980-03-31",
    "language": "nl",
    "street": "Dorpsstraat",
    "houseNumber": "12",
    "houseNumberAddition": "a",
    "postcode": "6811 AB",
    "city": "Arnhem",
    "country": "NL",
    "organisationContactId": null,
    "status": "active",
    "source": "api",
    "tags": ["Vrijwilliger"],
    "consents": {
      "email": null,
      "newsletter": { "granted": true, "at": "2026-10-18T09:12:00.000Z", "source": "api", "text": "Ja, ik wil de nieuwsbrief" },
      "phone": null,
      "post": null
    },
    "giving": { "totalCents": 0, "count": 0, "firstAt": null, "lastAt": null },
    "deceasedAt": null,
    "createdAt": "2026-10-18T09:12:00.000Z",
    "updatedAt": "2026-10-18T09:12:00.000Z"
  }
}

Contacten ophalen

GET/v1/contacts

ParameterBetekenis
qZoekt in naam, e-mail, telefoon, plaats en postcode.
emailPrecies dit e-mailadres (hoofdletters maken niet uit).
tagContacten met deze tag (de naam).
consentMet toestemming voor email, newsletter, phone of post.
kindperson of organisation.
statusStandaard actief en overleden; deceased voor alleen overleden contacten.
updatedSinceGewijzigd sinds dit moment: om een ander systeem bij te werken.
sortname (standaard), newest of updated.
page, limitBladeren; limit is hoogstens 100.

In de lijst staan contacten zonder consents; die krijg je bij één contact.

GET/v1/contacts/{id}

Een contact maken

POST/v1/contacts

Een persoon heeft een voornaam, achternaam of e-mailadres nodig; een organisatie (kind: "organisation") een organisationName. Postcodes worden netjes geschreven (6811 AB), e-mailadressen in kleine letters. Met tags (namen; nieuwe worden gemaakt) en consents leg je meteen toestemming vast, met de tekst waarmee die is gegeven in consentText.

curl
curl https://api.giift.nl/v1/contacts \
  -H "Authorization: Bearer $GIIFT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "firstName": "Anna",
    "lastName": "de Vries",
    "email": "anna@example.nl",
    "postcode": "6811AB",
    "houseNumber": "12",
    "tags": ["Vrijwilliger"],
    "consents": { "newsletter": true },
    "consentText": "Ja, ik wil de nieuwsbrief"
  }'

Bestaat er al een contact met het e-mailadres, dan krijg je 409 conflict met het id in de melding. Zoek dus eerst:

JavaScript: zoeken of maken
const headers = {
  Authorization: `Bearer ${process.env.GIIFT_API_KEY}`,
  "Content-Type": "application/json",
};
const email = "anna@example.nl";

// Find by email address first; create the contact when there is none.
const found = await fetch(`https://api.giift.nl/v1/contacts?email=${encodeURIComponent(email)}`, { headers });
const { data } = await found.json();
const contact = data[0] ?? (await (await fetch("https://api.giift.nl/v1/contacts", {
  method: "POST",
  headers,
  body: JSON.stringify({ firstName: "Anna", lastName: "de Vries", email }),
})).json()).data;

Een contact wijzigen

PATCH/v1/contacts/{id}

Alleen de velden die je meestuurt veranderen; null maakt een veld leeg. tags is de volledige lijst: tags die er niet in staan gaan eraf. Met status zet je een contact op deceased (overleden) of weer op active.

curl
curl -X PATCH https://api.giift.nl/v1/contacts/4821 \
  -H "Authorization: Bearer $GIIFT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "city": "Nijmegen", "phone": null, "tags": ["Vrijwilliger", "Gala 2026"] }'

Samenvoegen

POST/v1/contacts/{id}/merge

Met { "mergeId": 4822 } gaat contact 4822 op in dit contact: lege velden worden aangevuld, tags, toestemmingen en tijdlijn gaan mee, en 4822 wordt verwijderd.

Verwijderen

DELETE/v1/contacts/{id}

Voor een verzoek om vergeten te worden: de persoonsgegevens, toestemmingen, tags en de tijdlijn worden verwijderd. De bedragen van giften blijven, zonder naam, voor de boekhouding. Daarna geeft het contact 404.

Toestemming, notities en tijdlijn

PUT/v1/contacts/{id}/consents

Toestemming geldt per kanaal (email, newsletter, phone, post): true is gegeven, false ingetrokken. Kanalen die je niet meestuurt blijven zoals ze zijn. Giift bewaart elke wijziging met bron, datum en de text die de persoon zag; met at geef je een eerder moment op.

curl
curl -X PUT https://api.giift.nl/v1/contacts/4821/consents \
  -H "Authorization: Bearer $GIIFT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "newsletter": false, "text": "Afgemeld via onze website" }'

POST/v1/contacts/{id}/notes

Een notitie (note), telefoongesprek (call), e-mail (email) of afspraak (meeting) op de tijdlijn.

curl
curl https://api.giift.nl/v1/contacts/4821/notes \
  -H "Authorization: Bearer $GIIFT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "type": "call", "body": "Bedankt voor de gift; wil graag het jaarverslag." }'

GET/v1/contacts/{id}/timeline

Alles wat er met het contact is gebeurd, nieuwste eerst: notities, wijzigingen, toestemming, imports en samenvoegingen. by is wie het deed.

Campagnes

Een campagne is waarvoor een gift is: een doel met een naam, een beschrijving voor de bedankmail, een streefbedrag en een periode. Het id begint met cmp_.

GET/v1/campaigns

POST/v1/campaigns

curl
curl https://api.giift.nl/v1/campaigns \
  -H "Authorization: Bearer $GIIFT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Noodhulp honden",
    "description": "Voor honden in nood.",
    "goalCents": 500000,
    "endsAt": "2026-12-31T23:59:00+01:00",
    "publicTotal": true
  }'

GET/v1/campaigns/{id}

PATCH/v1/campaigns/{id}

Wijzigt de velden die je meestuurt. Met archived: true zet je een campagne weg; haal gearchiveerde op met ?archived=true.

Opgehaald bedrag tonen

GET/v1/campaigns/{id}/totals

Opgehaald, aantal gevers en voortgang. Zonder sleutel, vanaf elke website, voor campagnes met publicTotal: true; een minuut gecachet. De bedragen lopen op zodra giften via Giift binnenkomen.

Op je website
<p id="raised">…</p>
<script>
  fetch("https://api.giift.nl/v1/campaigns/cmp_k7Qx2mPa9rTb/totals")
    .then((response) => response.json())
    .then(({ data }) => {
      const euros = new Intl.NumberFormat("nl-NL", { style: "currency", currency: "EUR" });
      document.getElementById("raised").textContent =
        `${euros.format(data.raisedCents / 100)} van ${euros.format(data.goalCents / 100)}`;
    });
</script>

Webhooks

Met een webhook stuurt Giift een bericht naar jouw systeem zodra er iets gebeurt. Voeg er een toe bij Account → API of via de API.

GET/v1/webhooks

POST/v1/webhooks

curl
curl https://api.giift.nl/v1/webhooks \
  -H "Authorization: Bearer $GIIFT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.org/hooks/giift",
    "events": ["contact.created", "contact.updated", "contact.deleted"]
  }'

Het antwoord bevat het secret waarmee je controleert dat berichten van Giift komen; via de API zie je het alleen dan. De URL moet https zijn en openbaar bereikbaar.

DELETE/v1/webhooks/{id}

Gebeurtenissen

GebeurtenisWanneer
contact.createdEen contact is gemaakt, in Giift, door een import of via de API.
contact.updatedGegevens, tags, toestemming of status zijn gewijzigd.
contact.deletedEen contact is verwijderd (geanonimiseerd) of samengevoegd; dan staat mergedInto erbij.
donation.paidEen gift is betaald. Komt met de giften in Giift.
donation.failedEen gift of incasso is mislukt. Komt met de giften in Giift.
donation.refundedEen gift is terugbetaald of gestorneerd. Komt met de giften in Giift.
pledge.createdEen periodieke gift is gestart. Komt met periodieke giften.
pledge.stoppedEen periodieke gift is gestopt. Komt met periodieke giften.
Een bericht
{
  "id": "evt_Qm3xT9a2LpZk",
  "type": "contact.updated",
  "createdAt": "2026-10-18T09:15:42.000Z",
  "data": {
    "contact": { "id": 4821, "firstName": "Anna", "...": "..." }
  }
}
  • Giift stuurt een POST met de headers Giift-Event, Giift-Delivery en Giift-Signature.
  • Antwoord binnen 10 seconden met een 2xx. Anders probeert Giift het opnieuw, met steeds meer tijd ertussen, tot ongeveer twee dagen lang.
  • Hetzelfde bericht kan vaker aankomen: gebruik id om dubbele te herkennen. Na één wijziging kunnen meerdere contact.updated-berichten komen; ze bevatten steeds de nieuwste stand.
  • Een webhook die blijft mislukken zet Giift uit; zet hem weer aan bij Account → API.

De handtekening controleren

Giift-Signature is t=<tijdstip>,v1=<handtekening>: een HMAC-SHA256 van het tijdstip, een punt en de body, met je webhookgeheim. Weiger berichten met een andere handtekening of een tijdstip van meer dan vijf minuten geleden.

Node.js
import { createHmac, timingSafeEqual } from "node:crypto";

// In your route: read the body as text, exactly as it arrived.
export function isFromGiift(body, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const age = Math.abs(Date.now() / 1000 - Number(parts.t));
  if (!parts.v1 || age > 300) return false; // older than 5 minutes: refuse
  const expected = createHmac("sha256", secret).update(`${parts.t}.${body}`).digest("hex");
  return expected.length === parts.v1.length &&
    timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

// isFromGiift(rawBody, request.headers["giift-signature"], process.env.GIIFT_WEBHOOK_SECRET)

Privacy

  • Je organisatie is verantwoordelijk voor de gegevens van haar contacten; Giift verwerkt ze voor jou. Haal alleen op wat je nodig hebt.
  • Elk bekijken, wijzigen en verwijderen via de API staat in het logboek bij Account → Privacy en logboek.
  • Webhookberichten bevatten persoonsgegevens: zorg dat de ontvanger ze net zo zorgvuldig bewaart. Giift verwijdert verzonden berichten na 30 dagen.
  • Een inzageverzoek of verzoek om vergeten te worden handel je af met GET /v1/contacts/{id} en DELETE /v1/contacts/{id}.

OpenAPI

De volledige beschrijving van de API staat als OpenAPI 3.1 op https://api.giift.nl/v1/openapi.json, voor tools als Postman en Insomnia en om code te genereren.

Wijzigingen

DatumWijziging
2026-10-18Eerste versie: sleutels met bereik, contacten, toestemming, notities, tijdlijn, campagnes en webhooks.