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
- Maak een API-sleutel bij Account → API. Alleen de eigenaar en beheerders van een organisatie kunnen dat.
- Kies wat de sleutel mag: alles, alleen lezen, of alleen formulieren (voor Miix Donate in WordPress).
- Stuur de sleutel mee in de header
Authorization: Bearer giift_sk_…bij elk verzoek naarhttps://api.giift.nl/v1.
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:
| Bereik | Mag |
|---|---|
full | Alles: lezen en wijzigen van contacten, campagnes en webhooks. |
read | Alleen lezen: contacten, tijdlijn, campagnes en webhooks ophalen. |
checkout | Alleen 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/jsonmee. - Bedragen staan altijd in hele centen (
goalCents: 500000is € 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,totalPagesentotalCount. Blader met?page=2&limit=100(maximaal 100). - Per sleutel zijn 120 verzoeken per minuut toegestaan. Daarboven krijg je
429met eenRetry-After-header in seconden. - Binnen
/v1komen er alleen dingen bij: nieuwe velden, endpoints en gebeurtenissen. Negeer velden die je niet kent.
{
"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.
{
"error": {
"code": "invalid_request",
"message": "The contact is invalid. See details.",
"details": [
{ "path": "email", "message": "invalid_email" },
{ "path": "birthday", "message": "invalid_date" }
]
}
}| Status | Code | Betekenis |
|---|---|---|
| 400/422 | invalid_request | Het verzoek klopt niet; zie details. |
| 401 | unauthorized | Geen of een ongeldige sleutel. |
| 403 | forbidden | Het bereik van de sleutel staat dit niet toe. |
| 403 | account_blocked | De organisatie is geblokkeerd; neem contact met ons op. |
| 404 | not_found | Bestaat niet, of hoort bij een andere organisatie. |
| 409 | conflict | Botst met iets dat er al is, zoals een e-mailadres. |
| 429 | rate_limited | Te veel verzoeken; wacht Retry-After seconden. |
| 500 | server_error | Er 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).
{
"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
| Parameter | Betekenis |
|---|---|
q | Zoekt in naam, e-mail, telefoon, plaats en postcode. |
email | Precies dit e-mailadres (hoofdletters maken niet uit). |
tag | Contacten met deze tag (de naam). |
consent | Met toestemming voor email, newsletter, phone of post. |
kind | person of organisation. |
status | Standaard actief en overleden; deceased voor alleen overleden contacten. |
updatedSince | Gewijzigd sinds dit moment: om een ander systeem bij te werken. |
sort | name (standaard), newest of updated. |
page, limit | Bladeren; 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 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:
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 -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 -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 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 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.
<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 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
| Gebeurtenis | Wanneer |
|---|---|
contact.created | Een contact is gemaakt, in Giift, door een import of via de API. |
contact.updated | Gegevens, tags, toestemming of status zijn gewijzigd. |
contact.deleted | Een contact is verwijderd (geanonimiseerd) of samengevoegd; dan staat mergedInto erbij. |
donation.paid | Een gift is betaald. Komt met de giften in Giift. |
donation.failed | Een gift of incasso is mislukt. Komt met de giften in Giift. |
donation.refunded | Een gift is terugbetaald of gestorneerd. Komt met de giften in Giift. |
pledge.created | Een periodieke gift is gestart. Komt met periodieke giften. |
pledge.stopped | Een periodieke gift is gestopt. Komt met periodieke giften. |
{
"id": "evt_Qm3xT9a2LpZk",
"type": "contact.updated",
"createdAt": "2026-10-18T09:15:42.000Z",
"data": {
"contact": { "id": 4821, "firstName": "Anna", "...": "..." }
}
}- Giift stuurt een
POSTmet de headersGiift-Event,Giift-DeliveryenGiift-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
idom dubbele te herkennen. Na één wijziging kunnen meerderecontact.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.
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}enDELETE /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
| Datum | Wijziging |
|---|---|
| 2026-10-18 | Eerste versie: sleutels met bereik, contacten, toestemming, notities, tijdlijn, campagnes en webhooks. |