Skip to main content
Version: v1

Goodstack Services

Introduction

Goodstack API allows you to build good into your product by facilitating charitable donations. We achieve this through the API's listed and specified in this documentation.

For all donations using the Goodstack API, Goodstack handles the vetting and validation of the good causes and onward the disbursement of the donation.

The Goodstack API is organised around REST. All API responses, including errors, return JSON and use standard HTTP response codes. In all API requests you must specify the content-type HTTP header as application/json. All API requests must be made over HTTPS.

Authentication

Authenticate your API requests by setting your live secret API key in the Authorization header.

Two keys are provided to you, a publishable key prefixed with pk_ and a secret key prefixed with sk_.

Publishable API keys are meant solely to identify your goodstack account. These can safely be published in places like your JavaScript code, or in an Android or iPhone app and are meant for usage with our SDKs.

Secret API keys should be kept confidential and only used in secure server environments.

Anywhere that accepts a Publishable key will also accept a Secret key, but routes marked to accept a Secret key will not accept a Publishable key.

Formats

Dates​

Dates are encoded as strings following the ISO 8601 standard 2021-08-01T12:00:00.000Z.

Pagination​

A maximum of 100 objects can be returned per request and the limit can be specified by using the pageSize query parameter. By default, the pageSize is set to 25.

Metadata​

Some objects have a metadata parameter that you can use to store key-value data.

You can specify up to 20 keys, with key names up to 40 characters long and values up to 250 characters long.

Errors​

The API returns standard HTTP response codes to indicate success or failure of API requests. Errors may include a custom error message. Error objects have these attributes, an id, a message corresponding to the HTTP response phrase of the error, an optional invalid-params attribute detailing issues with the request.

An example error object returned from the API:

{
error: {
code: 'bad_request',
title: 'Bad request',
message: 'Something is wrong with your request, please check any parameters and try again',
reasons: ['amount is a required field']
}
}

A summary of the HTTP status codes returned from the api is listed in the table below.

CodeTitleDescription
200SuccessRequest successful
204No ContentRequest successful with no content returned
400Bad RequestRequest was invalid
401UnauthorisedAuthorization header is invalid
403ForbiddenInsufficient permissions
404Not FoundResource does not exist
429Too Many RequestsRate Limited
500sInternal Server ErrorSomething went wrong with the Goodstack API

Retry recommendations

Although rare, network requests may fail due to their inherent unreliability. To ensure a robust integration with Goodstack we recommend that you implement a retry mechanism, so that if a network request fails you are able to retry the request. Goodstacks API supports idempotency for safely retrying requests through usage of idempotency keys.

For some POST endpoints we allow clients to specify an idempotency key in the Idempotency-Key header. This allows the client to retry requests without accidentally performing the same operation twice.

If the same idempotency key is used for multiple requests, only the first successful request will be actioned and subsequent calls will return the result of the first request. Failed requests may be retried with the same idempotency key.

Requests with the same idempotency key and different payload will fail.

The idempotency key expires after 21 days. After this time, a new request with the same idempotency key will be treated as a new request.

Webhooks

Use webhooks to subscribe to updates on particular events that occur in Goodstack. Each time an event that you have subscribed to occurs, Goodstack submits a POST request to the designated webhook URL with information about the event.

To receive webhook notifications, use the webhook subscriptions API.

Attributes​

At the top level Webhook payloads will contain object and data fields. The data record contains the following fields:

ParameterTypeDescription
idstringId of the event
createdAtstringTimestamp of when the event was created
eventTypestringThe type of the event e.g. validation_request.approved
eventDatarecordData associated with the event

Webhook notifications​

The full list of IP addresses that webhook notifications may come from.

Production environment:

54.76.67.168
34.248.188.89
34.243.152.218

Sandbox environment:

54.76.168.240
99.81.243.145
54.220.118.167

Webhook responses​

To acknowledge receipt of a webhook notification, your endpoint must return a 2xx HTTP status code.

If the webhook is not received successfully then Goodstack will resend the webhook 4 times over the next 14 hours with increasing delays between retries.

The id of the event can be used to uniquely identify the event. We recommend making the processing of these events idempotent. While rare it is possible for multiple webhooks to be sent for the same event.

Verifying webhooks​

To verify that the webhook payload is sent from Goodstack, a header Goodstack-Signature is included in the request, this signature is generated using HMAC with SHA-256 hashing algorithm and Hex encoded. The webhook subscription secret is used as the key.

You can compute this signature using HMAC with the SHA-256 hash function, using the webhook subscription secret as the key, and the request body as the message. You can then check that the Goodstack-Signature value matches the computed value, verifying that the webhook was sent from Goodstack.

Validation Request events​

EventDescription
validation_request.approvedValidation request was approved
validation_request.rejectedValidation request was rejected
{
"object": "event",
"data": {
"id": "event_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"eventType": "validation_request.approved",
"createdAt": "2021-08-01T12:00:00.000Z",
"eventData": {
"id": "validationrequest_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"organisationId": "organisation_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"organisationTypes": ["nonprofit", "social_impact"],
"name": "example charity",
"registryName": "National Charities Registry",
"registryId": "I34324",
"email": "example@example.com",
"addressLine1": "example street 15",
"addressLine2": null,
"city": "Katowice",
"postal": "44-100",
"state": "Silesia",
"website": "https://example.com",
"countryCode": "POL",
"createdAt": "2021-08-01T12:00:00.000Z",
"deletedAt": null,
"acceptedAt": "2021-08-01T12:00:00.000Z",
"rejectedAt": null,
"(deprecated) rejectionReason": null,
"rejectionReasonCode": null
}
}
}

Agent Verification events​

EventDescription
agent_verification.pending_user_verificationAgent has passed Goodstack review, and is awaiting email verification.
agent_verification.pending_reviewAgent has passed email verification and is awaiting Goodstack review.
agent_verification.approvedAgent verification was approved.
agent_verification.rejectedAgent verification was rejected.
{
"object": "event",
"data": {
"id": "event_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"eventType": "agent_verification.approved",
"createdAt": "2021-08-01T12:00:00.000Z",
"eventData": {
"id": "agentverification_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"firstName": "Max",
"lastName": "Plank",
"organisationId": "organisation_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"email": "example@example.com",
"language": "en-GB",
"validationRequestId": "validationRequest_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"title": "Board Member",
"(deprecated) rejectionReason": "Identity check failed",
"rejectionReasonCode": "user_failed_percent_review",
"metadata": {},
"createdAt": "2021-08-01T12:00:00.000Z",
"status": "approved"
}
}
}

Monitoring Subscription events​

EventDescription
monitoring_subscription.updatedMonitoring Subscription was updated.
{
"object": "event",
"data": {
"id": "event_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"eventType": "monitoring_subscription.updated",
"createdAt": "2021-08-07T11:00:00.000Z",
"eventData": {
"id": "monitoringsubscription_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"organisationId": "organisation_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"validationRequestId": null,
"status": "live",
"createdAt": "2021-08-02T11:00:00.000Z",
"results": {
"complianceStatus": "fail",
"warning": { "status": "clear" },
"sanction": { "status": "flag" },
"hateSpeech": { "status": "clear" },
"commercial": { "status": "clear" },
"adverseMedia": { "status": "clear" },
"controversial": { "status": "flag" },
"registration": { "active": "yes" }
}
}
}
}

Eligibility Subscription events​

EventDescription
eligibility_subscription.updatedEligibility Subscription was updated.
{
"object": "event",
"data": {
"id": "event_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"eventType": "eligibility_subscription.updated",
"createdAt": "2021-08-07T11:00:00.000Z",
"eventData": {
"id": "eligibilitysubscription_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"organisationId": "organisation_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"validationRequestId": null,
"status": "live",
"suggestedActivitySubTags": [
{
"id": "activitysubtag_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"name": "Primary School"
"description": "Includes all primary schools",
"tag": {
"id": "activitytag_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"name": "Educational"
"description": "Includes all educational institutions"
}
}
]
"createdAt": "2021-08-02T11:00:00.000Z",
"updatedAt": "2021-08-02T11:00:00.000Z",
"results": {
"eligibilityStatus": "fail",
"confirmedActivitySubTags": [
{
"id": "activitysubtag_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"name": "Primary School"
"description": "Includes all primary schools",
"tag": {
"id": "activitytag_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"name": "Educational"
"description": "Includes all educational institutions"
}
}
],
"rejectedActivitySubTags": [
{
"id": "activitysubtag_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"name": "Primary School"
"description": "Includes all primary schools",
"tag": {
"id": "activitytag_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"name": "Educational"
"description": "Includes all educational institutions"
}
}
]
}
}
}
}

Validation Submission events​

EventDescription
validation_submission.createdValidation Submission was created.
validation_submission.succeededValidation Submission has passed all checks
validation_submission.failedValidation Submission has failed
validation_submission.updatedValidation Submission status has updated
{
"object": "event",
"data": {
"id": "event_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"eventType": "validation_submission.created",
"createdAt": "2021-08-07T11:00:00.000Z",
"eventData": {
"id": "validationsubmission_xxxxx",
"agentVerificationId": "agentverification_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"agentVerification": {
"id": "agentverification_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"firstName": "Max",
"lastName": "Plank",
"email": "example@example.com",
"(deprecated) rejectionReason": null,
"rejectionReasonCode": null,
"status": "pending"
},
"createdAt": "2021-08-02T11:00:00.000Z",
"eligibilitySubscriptionId": "eligibilitysubscription_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"monitoringSubscriptionId": "monitoringsubscription_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"organisationId": "organisation_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"partnerFields": {},
"validationSubmissionHostedConfigurationId": "hostedconfiguration_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"metadata": {},
"status": "pending",
"validationInviteId": "validationinvite_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"validationRequestId": "validationrequest_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"validationRequest": {
"id": "validationrequest_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"name": "Charity",
"acceptedAt": null,
"rejectedAt": null,
"organisationTypes": []
}
}
}
}

N.B. Please be aware that the "Validation Submission Failure" event consists of a property named failureReasons. In the given example, we have included all possible reasons that may be encountered. It is important to note that only one of these reasons will be received when encountering this event.

{
"object": "event",
"data": {
"id": "event_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"eventType": "validation_submission.failed",
"createdAt": "2021-08-07T11:00:00.000Z",
"eventData": {
"id": "validationsubmission_xxxxx",
"agentVerificationId": "agentverification_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"agentVerification": {
"id": "agentverification_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"firstName": "Max",
"lastName": "Plank",
"email": "example@example.com",
"(deprecated) rejectionReason": "Other",
"rejectionReasonCode": "other",
"status": "rejected"
},
"createdAt": "2021-08-02T11:00:00.000Z",
"eligibilitySubscriptionId": "eligibilitysubscription_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"monitoringSubscriptionId": "monitoringsubscription_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"organisationId": "organisation_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"partnerFields": {},
"validationSubmissionHostedConfigurationId": "hostedconfiguration_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"metadata": {},
"status": "failed",
"validationInviteId": "validationinvite_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"validationRequestId": "validationrequest_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"validationRequest": {
"id": "validationrequest_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"name": "Charity",
"acceptedAt": "2021-08-01T12:00:00.000Z",
"rejectedAt": null,
"organisationTypes": ["nonprofit"]
},
"failureReasons": [
{
"check": "validation_request",
"reason": {
"rejectionReasonCode": "other"
}
},
{
"check": "agent_verification",
"reason": {
"rejectionReasonCode": "fake_email_used"
}
},
{
"check": "compliance",
"reason": {
"results": {
"complianceStatus": "fail",
"warning": {
"status": "flag"
},
"sanction": {
"status": "flag"
},
"hateSpeech": {
"status": "flag"
},
"commercial": {
"status": "flag"
},
"adverseMedia": {
"status": "flag"
},
"controversial": {
"status": "flag"
},
"registration": {
"active": "yes"
}
}
}
},
{
"check": "eligibility",
"reason": {
"status": "cannot_define_eligibility",
"results": {
"eligibilityStatus": null,
"confirmedActivitySubTags": null,
"rejectedActivitySubTags": null
}
}
},
{
"check": "eligibility",
"reason": {
"status": "live",
"results": {
"eligibilityStatus": "fail",
"confirmedActivitySubTags": [{
"id": "tag1",
"name": "Tag 1",
"tag": {
"id": "tag1",
"name": "Tag 1",
"description": "Tag description"
},
"description": "Sub tag description"
}],
"rejectedActivitySubTags": [{
"id": "tag1",
"name": "Tag 1",
"tag": {
"id": "tag1",
"name": "Tag 1",
"description": "Tag description"
},
"description": "Sub tag description"
}]
}
}
}
]
}
}
}

Donation events​

PII fields (firstName, lastName & email) will be included or excluded depending on your partner settings.

EventDescription
donation.hosted.payment_receivedPayment has been received from the user for a hosted donation.
{
"object": "event",
"data": {
"id": "event_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"eventType": "donation.hosted.payment_received",
"createdAt": "2021-08-07T11:00:30.000Z",
"eventData": {
"id": "donation_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"userId": "user_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"organisationId": "organisation_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"type": "hosted",
"hosted": {
"sessionId": "donationsession_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
"amount": 10000,
"currencyCode": "AUD",
"status": "RECEIVED_PAYMENT",
"firstName": "Julia",
"lastName": "Russell",
"email": "example@example.com",
"consentedToBeContactedByOrg": "yes",
"anonymous": "no",
"giftAidId": null,
"cancelledAt": null,
"createdAt": "2021-08-07T11:00:00.000Z",
"metadata": {
"username": "jr123"
},
"subscription": {
"initial": true
}
}
}
}
EventDescription
donation.createdA new donation record has been successfully created in the system.
donation.payment_successfulThe payment for the donation has been successfully completed and verified.
donation.settledThe donated funds have been received and reconciled by the partner foundation.
donation.disbursedThe donated funds have been transferred to the intended nonprofit.
donation.cancelledThe donation process has been terminated, either by the donor or due to system issues.
donation.reassignedThe donation has been reassigned to another organisation, either because it has failed compliance, we are unable to pay the organisation or the organisation has been merged with another id.
{
"object": "event",
"data": {
"id": "event_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"eventType": "donation.payment_successful",
"createdAt": "2021-08-07T11:00:30.000Z",
"eventData": {
"id": "donation_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"userId": "user_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"organisationId": "organisation_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"type": "direct",
"amount": { "amount": 10, "currency": "GBP" },
"status": "ACTIVE",
"firstName": "Julia",
"lastName": "Russell",
"email": "example@example.com",
"consentedToBeContacted": "yes",
"anonymous": "no",
"giftAidId": "giftaid_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"createdAt": "2021-08-07T11:00:00.000Z",
"cancelledAt": null,
"donationRequestId": "donationrequest_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"donationRequestAmount": { "amount": 10, "currency": "GBP" },
"accountId": "account_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"metadata": {"data": "hash-value"},
"settledAmount": { amount: 10, currency: "GBP" },
"settledAt": "2021-08-07T11:00:00.000Z",
"disbursedAt": "2021-08-07T11:00:00.000Z",
"reassignedAt": "2021-08-07T11:00:00.000Z",
"reassignedFromOrganisationId": "organisation_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
}
}
}

Authentication​

API key starting with 'pk_' that can be made public

Security Scheme Type:

apiKey

Header parameter name:

Authorization