# Goodstack Docs > Developer documentation for Goodstack verification, donation, and giving products. Canonical OpenAPI specification: https://reference.poweredbypercent.com/openapi.json ## changelog Blog - [Blog](/changelog.md): Blog ### archive Archive - [Archive](/changelog/archive.md): Archive ### authors - [Authors](/changelog/authors.md) ### goodstack-launch As part of our continued growth in supporting impactful businesses and good causes around the world, we're excited to announce that Percent is rebranding as Goodstack! - [Percent is becoming Goodstack](/changelog/goodstack-launch.md): As part of our continued growth in supporting impactful businesses and good causes around the world, we're excited to announce that Percent is rebranding as Goodstack! ### tags - [Tags](/changelog/tags.md) - [One post tagged with "goodstack"](/changelog/tags/goodstack.md) - [One post tagged with "rebrand"](/changelog/tags/rebrand.md) ## search - [Search the documentation](/search.md) ## docs ### api - [Activities](/docs/api/activities.md): Activities - [Add a Organisation to a Category](/docs/api/add-a-organisation-to-a-category.md): Add a Organisation to a Category - [Agent Verification Events](/docs/api/agent-verification-events.md): Agent Verification Events - [Agent Verifications](/docs/api/agent-verifications.md): Agent Verifications - [Apply to a grant program](/docs/api/apply-to-a-grant-program.md): Creates a candidate for an application-type grant program. Needs no credentials. Requests are rate limited per IP address. - [Budgets](/docs/api/budgets.md): Budgets - [Bulk operations](/docs/api/bulk-operations.md): Apply up to 100 user operations in a single request. Each operation is a - [Campaigns](/docs/api/campaigns.md): Campaigns - [Cancel a Donation](/docs/api/cancel-a-donation.md): Applicable only to `direct` type Donations created directly through the [Create A Donation API](./create-a-donation). - [Cancel a Monitoring Subscription](/docs/api/cancel-a-monitoring-subscription.md): Cancel a Monitoring Subscription - [Cancel an Eligibility Subscription](/docs/api/cancel-an-eligibility-subscription.md): Cancel an Eligibility Subscription - [Categories](/docs/api/categories.md): Categories - [Countries](/docs/api/countries.md): Countries - [Country Compliance Check Events](/docs/api/country-compliance-check-events.md): Country Compliance Check Events - [Create a Category](/docs/api/create-a-category.md): Create a Category - [Create a donation](/docs/api/create-a-donation.md): Creates a new Donation of type `direct`. We will request payment from you for the Donations aggregated over a period. Goodstack and our associated foundations will then handle the ongoing safe and efficient disbursement of funds to the nonprofit. - [Create a Donation Session](/docs/api/create-a-donation-session.md): To create a Donation Session you must supply the `organisationId` of the nonprofit you want to let your user donate to. - [Create a GiftAid Declaration](/docs/api/create-a-gift-aid-declaration.md): Create a GiftAid Declaration - [Create a Monetary Donation Verification](/docs/api/create-a-monetary-donation-verification.md): Creates a new monetary donation verification for a partner. - [Create a Monitoring Subscription](/docs/api/create-a-monitoring-subscription.md): Create a Monitoring Subscription - [Create a User](/docs/api/create-a-user.md): Create a User - [Create a Validation Invite](/docs/api/create-a-validation-invite.md): Create a Validation Invite - [Create a Validation Request](/docs/api/create-a-validation-request.md): Create a Validation Request - [Create a Validation Request Document](/docs/api/create-a-validation-request-document.md): Uploads a document and updates the Validation Request to refer to that document. Valid file formats for documents are `jpg`, `png` and `pdf`. The maximum file size is 5MB. `heic`/`heif` photos are accepted too and are converted to `jpg` on upload. - [Create a Validation Submission](/docs/api/create-a-validation-submission.md): Create a Validation Submission - [Create a Webhook Subscription](/docs/api/create-a-webhook-subscription.md): Create a Webhook Subscription. Specify the event types to subscribe to and the URL where the notifications will be sent, the URL must be https. - [Create an Agent Verification](/docs/api/create-an-agent-verification.md): Create an Agent Verification - [Create an Eligibility Subscription](/docs/api/create-an-eligibility-subscription.md): Create an Eligibility Subscription - [Create Validation Submission Document](/docs/api/create-validation-submission-document.md): Uploads a Validation Submission document. Valid file formats for documents are `jpg`, `png` and `pdf`. The maximum file size is 5MB. `heic`/`heif` photos are accepted too and are converted to `jpg` on upload. - [Deactivate a user](/docs/api/deactivate-a-user.md): Soft-delete (deactivate) a user. The user must belong to the partner the SCIM key is bound to. - [Delete a Category](/docs/api/delete-a-category.md): Delete a Category - [Delete a Webhook Subscription](/docs/api/delete-a-webhook-subscription.md): Delete a Webhook Subscription - [Donation Events](/docs/api/donation-events.md): Donation Events - [Donation Match Request](/docs/api/donation-match-request.md): Donation Match Request - [Donation Sessions](/docs/api/donation-sessions.md): Donation Sessions - [Donation Wallets](/docs/api/donation-wallets.md): Donation Wallets - [Donations](/docs/api/donations.md): Donations - [Edit a Category](/docs/api/edit-a-category.md): Edit a Category - [Edit a Donation](/docs/api/edit-a-donation.md): Update the recipient organisation, metadata of a Donation. The recipient organisation can only be changed for `direct` type Donations created directly through the [Create A Donation API](./create-a-donation) that are in an `ACTIVE` status. - [Edit a Webhook Subscription](/docs/api/edit-a-webhook-subscription.md): Edit a Webhook Subscription - [Edit an Agent Verification](/docs/api/edit-an-agent-verification.md): Update an Agent Verifications metadata. - [Edit an Eligibility Subscription](/docs/api/edit-an-eligibility-subscription.md): Update an Eligibility Subscription's metadata. - [Eligibility Subscription Events](/docs/api/eligibility-subscription-events.md): Eligibility Subscription Events - [Eligibility Subscriptions](/docs/api/eligibility-subscriptions.md): Eligibility Subscriptions - [Employees](/docs/api/employees.md): Employees - [Fundraising](/docs/api/fundraising.md): Fundraising - [Gamification](/docs/api/gamification.md): Gamification - [Get a Monetary Donation Verification by ID](/docs/api/get-a-monetary-donation-verification-by-id.md): Retrieves a monetary donation verification by its ID for the authenticated partner. - [Get organisation branding](/docs/api/get-organisation-branding.md): Retrieve branding information for a specific organisation - [List candidates for a grant program](/docs/api/get-program-candidates.md): Fetches paginated candidate data for a grant program, enriched with organisation details, state transitions, and form submissions. Requires partner secret key authentication and the Grants scope. - [List form questions for a grant program](/docs/api/get-program-form-questions.md): Returns the form questions of each task template in the grant program, grouped by task template. Document questions are not included. Task templates without questions are not included. Task templates with the external audience are last. Requires the Grants scope and the grants:read permission. - [List task assignees for the partner](/docs/api/get-task-assignees.md): Returns the accounts that the task auto-assignment rules of the partner assign tasks to. Requires the Grants scope and the grants:read permission. - [Get Validation Submission Documents](/docs/api/get-validation-submission-documents.md): Get Validation Submission Documents - [Gift Aid](/docs/api/gift-aid.md): Gift Aid - [Giving Donations](/docs/api/giving-donations.md): Giving Donations - [Goodstack Services](/docs/api/goodstack-services.md): # Introduction - [Grants](/docs/api/grants.md): Grants - [List Account Achievements](/docs/api/list-account-achievements.md): Get a paginated list of Account Achievements - [List Account Budget state changes](/docs/api/list-account-budget-state-changes.md): Returns a list of all changes to an account's budget over time. Each entry represents - [List Activity Tags](/docs/api/list-activity-tags.md): List Activity Tags - [List Agent Verifications](/docs/api/list-agent-verifications.md): List Agent Verifications - [List an Organisations Milestones](/docs/api/list-an-organisations-milestones.md): List an Organisations Milestones - [List available Countries](/docs/api/list-available-countries.md): This endpoint will return all the Countries that Goodstack currently supports. If hasRegistry is true, it will return the subset of Countries that Goodstack currently has nonprofit data for. - [List Campaigns](/docs/api/list-campaigns.md): Get a paginated list of Campaigns - [List Categories](/docs/api/list-categories.md): List Categories - [List Donation Match Requests](/docs/api/list-donation-match-requests.md): Get a paginated list of Donation Match Requests for the authenticated partner. - [List Donation Sessions](/docs/api/list-donation-sessions.md): List Donation Sessions - [List Donation Wallet state changes](/docs/api/list-donation-wallet-state-changes.md): Returns a list of all state changes to a donation wallet's balance over time. Each entry - [List Donations](/docs/api/list-donations.md): Retrieve a list of donations. - [List Eligibility Subscriptions](/docs/api/list-eligibility-subscriptions.md): List Eligibility Subscriptions - [List Fundraisers](/docs/api/list-fundraisers.md): Get a paginated list of Fundraisers - [List Donations](/docs/api/list-giving-donations.md): Retrieve a paginated list of donations with optional filtering and organisation expansion. - [List Monitoring Subscriptions](/docs/api/list-monitoring-subscriptions.md): List Monitoring Subscriptions - [List Registries](/docs/api/list-registries.md): List Registries - [List resource types](/docs/api/list-resource-types.md): Discovery endpoint listing the supported resource types. The `User` - [List supported schemas](/docs/api/list-supported-schemas.md): Discovery endpoint listing the supported SCIM schemas: the Core User, - [List users](/docs/api/list-users.md): Return a paginated, optionally filtered and sorted list of users that - [List Validation Requests](/docs/api/list-validation-requests.md): List Validation Requests - [List Validation Submissions](/docs/api/list-validation-submissions.md): List Validation Submissions - [List Volunteering Opportunities](/docs/api/list-volunteering-opportunities.md): Get a paginated list of Volunteering Opportunities - [List Volunteering Opportunity Participants](/docs/api/list-volunteering-opportunity-participants.md): Get a paginated list of Volunteering Opportunity Participants - [List Volunteering Time Logs](/docs/api/list-volunteering-time-logs.md): Get a paginated list of Volunteering Time Logs - [List Webhook Subscriptions](/docs/api/list-webhook-subscriptions.md): List Webhook Subscriptions - [List Workplace Giving Employee Profiles](/docs/api/list-workplace-giving-employee-profiles.md): List Workplace Giving Employee Profiles - [Monetary Donation Verification Events](/docs/api/monetary-donation-verification-events.md): Monetary Donation Verification Events - [Monetary Donation Verifications](/docs/api/monetary-donation-verifications.md): Monetary Donation Verifications - [Monitoring Subscription Events](/docs/api/monitoring-subscription-events.md): Monitoring Subscription Events - [Monitoring Subscriptions](/docs/api/monitoring-subscriptions.md): Monitoring Subscriptions - [Org name search](/docs/api/org-name-search.md): Org name search - [Organisation](/docs/api/organisation.md): Organisation - [Organisation Branding](/docs/api/organisation-branding.md): Organisation Branding - [Organisations](/docs/api/organisations.md): Organisations - [Registries](/docs/api/registries.md): Registries - [Remove a Organisation from a Category](/docs/api/remove-a-organisation-from-a-category.md): Remove a Organisation from a Category - [Replace a user](/docs/api/replace-a-user.md): Replace a user's attributes. Attributes omitted from the request are - [Retrieve a Category](/docs/api/retrieve-a-category.md): Retrieve a Category - [Retrieve a Donation](/docs/api/retrieve-a-donation.md): Retrieve a donation by its ID. - [Retrieve a Monitoring Subscription](/docs/api/retrieve-a-monitoring-subscription.md): Retrieve a Monitoring Subscription - [Retrieve a Registry](/docs/api/retrieve-a-registry.md): Retrieve a Registry - [Retrieve a schema](/docs/api/retrieve-a-schema.md): Retrieve a single schema definition by its URN. - [Retrieve a User](/docs/api/retrieve-a-user.md): Retrieve a User - [Retrieve a Validation Request](/docs/api/retrieve-a-validation-request.md): Retrieve a Validation Request - [Retrieve a Validation Submission](/docs/api/retrieve-a-validation-submission.md): Retrieve a Validation Submission - [Retrieve a Webhook Subscription](/docs/api/retrieve-a-webhook-subscription.md): Retrieve a Webhook Subscription - [Retrieve an Activity Tag](/docs/api/retrieve-an-activity-tag.md): Retrieve an Activity Tag - [Retrieve an Agent Verification](/docs/api/retrieve-an-agent-verification.md): Retrieve an Agent Verification - [Retrieve an Organisation](/docs/api/retrieve-an-organisation.md): Retrieve an Organisation - [Retrieve an Organisation by country code, registry and registryId](/docs/api/retrieve-an-organisation-by-country-code-registry-and-registry-id.md): Retrieve an Organisation by country code, registry and registryId - [Retrieve an Organisation profile info](/docs/api/retrieve-an-organisation-profile-info.md): Retrieve an Organisation profile info - [Retrieve an single Eligibility Subscription](/docs/api/retrieve-an-single-eligibility-subscription.md): Retrieve an single Eligibility Subscription - [Retrieve donation session](/docs/api/retrieve-donation-session.md): Retrieve donation session - [Retrieve GiftAid id for User](/docs/api/retrieve-gift-aid-id-for-user.md): Returns the most recent gift aid id for a given user, returns 404 not found if no gift aid exists. - [Retrieve partner's configurations](/docs/api/retrieve-partners-configurations.md): Retrieve partner's configurations - [Retrieve service provider configuration](/docs/api/retrieve-service-provider-configuration.md): Discovery endpoint describing the SCIM features supported by the service - [Retrieve specific configuration](/docs/api/retrieve-specific-configuration.md): Retrieve specific configuration - [SCIM](/docs/api/scim.md): SCIM - [Create a user](/docs/api/scim-create-user.md): Provision a new user for the partner the SCIM key is bound to. - [Retrieve a user](/docs/api/scim-retrieve-user.md): Retrieve a single user by id. The user must belong to the partner the SCIM key is bound to. - [Search for Validation Submissions with specific org name](/docs/api/search-for-validation-submissions-with-specific-org-name.md): Search for Validation Submissions with specific org name - [Search Organisations](/docs/api/search-organisations.md): Search for nonprofits. The Organizations Search API provides up to 1,000 results for each search query, accessible via pagination. - [Set an Image for a Category](/docs/api/set-an-image-for-a-category.md): Set an Image for a Category - [Set User to support an Organisation](/docs/api/support-organisation.md): For Donations created through the [Create A Donation API](./create-a-donation), A User can be set to donate to a particular Organisation by default using this API. If a Donation is made with a userId, the Donation will be associated with the Organisation the User is supporting at the time of Donation. If the userId is omitted or the User has not been set to donate to an Organisation by default then the organisationId must be included in the body. - [Update a user](/docs/api/update-a-user.md): Apply a SCIM PATCH operation set to a user. - [Update donor details for donation session](/docs/api/update-donor-details-for-donation-session.md): Updates the email address, name, consent preferences, and/or gift note for any donations linked to the specified donation session. - [Users](/docs/api/users.md): Users - [Validation Invites](/docs/api/validation-invites.md): Validation Invites - [Validation Request Documents](/docs/api/validation-request-documents.md): Validation Request Documents - [Validation Request Events](/docs/api/validation-request-events.md): Validation Request Events - [Validation Requests](/docs/api/validation-requests.md): Validation Requests - [Validation Submission Configuration](/docs/api/validation-submission-configuration.md): Validation Submission Configuration - [Validation Submission Documents](/docs/api/validation-submission-documents.md): Validation Submission Documents - [Validation Submission Events](/docs/api/validation-submission-events.md): Validation Submission Events - [Validation Submissions](/docs/api/validation-submissions.md): Validation Submissions - [Volunteering](/docs/api/volunteering.md): Volunteering - [Agent verification was approved](/docs/api/webhook-agent-verification-approved.md): Triggered when an agent verification has been approved by Goodstack. - [Agent verification pending review](/docs/api/webhook-agent-verification-pending-review.md): Agent has passed email verification and is awaiting Goodstack review. - [Agent verification pending user verification](/docs/api/webhook-agent-verification-pending-user-verification.md): Agent has passed Goodstack review, and is awaiting email verification. - [Agent verification was rejected](/docs/api/webhook-agent-verification-rejected.md): Triggered when an agent verification has been rejected by Goodstack. - [Country compliance check result was stored](/docs/api/webhook-country-compliance-check-updated.md): Triggered when the result of the country compliance check is stored for an organisation in one of - [Donation cancelled](/docs/api/webhook-donation-cancelled.md): The donation process has been terminated, either by the donor or due to system issues. - [Donation record created](/docs/api/webhook-donation-created.md): A new donation record has been successfully created in the system. - [Donation disbursed](/docs/api/webhook-donation-disbursed.md): The donated funds have been transferred to the intended nonprofit. - [Payment received for hosted donation](/docs/api/webhook-donation-hosted-payment-received.md): Payment has been received from the user for a hosted donation. - [Donation payment successful](/docs/api/webhook-donation-payment-successful.md): The payment for the donation has been successfully completed and verified. - [Donation reassigned](/docs/api/webhook-donation-reassigned.md): The 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. - [Donation settled](/docs/api/webhook-donation-settled.md): The donated funds have been received and reconciled by the partner foundation. - [Eligibility subscription was updated](/docs/api/webhook-eligibility-subscription-updated.md): Triggered when an eligibility subscription has been updated. - [Monetary donation verification progressed](/docs/api/webhook-monetary-donation-verification-progressed.md): Triggered when a monetary donation verification has progressed, such as a check succeeding, failing, the verification being approved, rejected, expired, or reopened. - [Monetary donation verification received](/docs/api/webhook-monetary-donation-verification-received.md): Triggered when a new monetary donation verification has been received and created. - [Monitoring subscription was updated](/docs/api/webhook-monitoring-subscription-updated.md): Triggered when a monitoring subscription has been updated. - [Webhook Subscriptions](/docs/api/webhook-subscriptions.md): Webhook Subscriptions - [Validation request was approved](/docs/api/webhook-validation-request-approved.md): Triggered when a validation request has been approved by Goodstack. - [Validation request was rejected](/docs/api/webhook-validation-request-rejected.md): Triggered when a validation request has been rejected by Goodstack. - [Validation submission was created](/docs/api/webhook-validation-submission-created.md): Triggered when a validation submission has been created. - [Validation submission has failed](/docs/api/webhook-validation-submission-failed.md): Triggered when a validation submission has failed validation checks. - [Validation submission has passed all checks](/docs/api/webhook-validation-submission-succeeded.md): Triggered when a validation submission has successfully passed all validation checks. - [Validation submission status has updated](/docs/api/webhook-validation-submission-updated.md): Triggered when a validation submission status has been updated. ### concepts Refer to the pages in this section for standard values, conventions and options for configuration and testing used across our product stack. - [Concepts](/docs/concepts.md): Refer to the pages in this section for standard values, conventions and options for configuration and testing used across our product stack. - [Currency reference](/docs/concepts/Donations/currencies.md): Account currency - [Localization](/docs/concepts/Donations/donation-locales.md): The following locales are supported in our donation API: - [Test cards](/docs/concepts/Donations/test-cards.md): The following card details can be used to test various different card transaction outcomes in the Hosted Donation Gateway sandbox environment. - [Test organizations](/docs/concepts/Donations/test-orgs.md): The following organisations are available for making donations in the sandbox. You can use these to test your integration. - [Countries](/docs/concepts/Platform/countries.md): The following countries are supported by our APIs and can also be retrieved via the List Countries endpoint. - [Formats](/docs/concepts/Platform/formats.md): Dates - [Webhooks](/docs/concepts/Platform/webhooks.md): 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. - [Additional fields](/docs/concepts/Verifications/additional-fields.md): This feature only applies to the hosted setup - [Applications inbox](/docs/concepts/Verifications/applications-inbox.md): Applications inbox is where you can see all the applications that came in for your product - [Agent Verification](/docs/concepts/Verifications/Checks/agent-verification.md): Agent Verification - [Compliance Checks](/docs/concepts/Verifications/Checks/compliance-checks.md): Compliance Checks - [Eligibility Review](/docs/concepts/Verifications/Checks/eligibility-review.md): Eligibility Review - [Validations Overview](/docs/concepts/Verifications/Checks/validations-overview.md): Validations Overview - [Discount redemption](/docs/concepts/Verifications/discount-redemption.md): This feature only applies to the hosted setup - [Outcome Emails](/docs/concepts/Verifications/outcome-emails.md): Outcome Emails ### guides Choose the guide that matches the workflow you want to build. Hosted guides are the fastest route to launch; API guides give you more control over the user experience. - [Solution guides](/docs/guides.md): Choose the guide that matches the workflow you want to build. Hosted guides are the fastest route to launch; API guides give you more control over the user experience. - [Nonprofit verification](/docs/guides/Cause verification.md): Use Goodstack to confirm that an organisation is a nonprofit or other approved cause, check the - [Nonprofit verification: API](/docs/guides/Cause verification/verification-api.md): Build a server-side nonprofit verification flow with Goodstack APIs. - [Nonprofit verification – hosted form implementation](/docs/guides/Cause verification/verification-hosted.md): The hosted setup for your nonprofit discount program enables you to have it set up in no time and with barely any engineering resource. - [Education Verification](/docs/guides/Education verification.md): Education Verification – Overview - [Education verification: API](/docs/guides/Education verification/edu-verification-api.md): Overview - [Monetary Donations – Overview](/docs/guides/Monetary donations.md): The Goodstack API enables you to leverage Goodstack’s global donation infrastructure in your product. You can ether fully integrate Goodstack into your product using our direct APIs or save time by using the Goodstack Hosted Donation Gateway. - [Donations API setup](/docs/guides/Monetary donations/donations-api.md): The API setup for monetary donations allows you to build a totally custom donation experience that will live in your product. If you don’t need a custom setup and are limited in engineering resource please have a look at our hosted setup. - [Donations hosted gateway setup](/docs/guides/Monetary donations/donations-hosted.md): The Goodstack Hosted Donation Gateway enables you to create a branded donation experience in your product, using only a few lines of code. ### use-cases Discover Goodstack use cases in this section. - [Use cases](/docs/use-cases.md): Discover Goodstack use cases in this section. - [Fundraising](/docs/use-cases/fundraising.md): Online charitable donations and fundraising have become increasingly popular methods for organizations to raise funds for their causes. With the rise of social media and online platforms, it has become easier than ever for individuals to donate to their favorite charities and for organizations to reach a larger audience. Online fundraising is a convenient and efficient way to support causes that you care about and make a positive impact on the world. - [Impact programs](/docs/use-cases/impact-program.md): Impact programs can support eligible good causes, through software discount and credit programs that can help them save money on the software they need for their day-to-day operations. - [Workplace giving](/docs/use-cases/workplace-giving.md): Workplace giving programs are initiatives that allow employees to donate to charitable organizations through their employer. These programs provide a convenient way for employees to support causes they care about while also encouraging a sense of community and social responsibility within the workplace. Workplace giving can take many forms, including payroll deductions, employer matching programs, and volunteer opportunities. By participating in workplace giving, employees can make a positive impact on their communities and beyond. ## Optional - [OpenAPI specification (JSON)](https://reference.poweredbypercent.com/openapi.json): Canonical OpenAPI specification for the Goodstack API --- # Full Documentation Content ## [Percent is becoming Goodstack](/changelog/goodstack-launch.md) October 15, 2024 · One min read [![Stefan Greer](/img/authors/stef.jpeg)](https://uk.linkedin.com/in/stefangreer) [Stefan Greer](https://uk.linkedin.com/in/stefangreer) COO As part of our continued growth in supporting impactful businesses and good causes around the world, we're excited to announce that Percent is rebranding as Goodstack! Goodstack Docs are our new unified documentation hub for developers, impact teams and nonprofits to help you design, build and optimise the tools you need to drive your mission using Goodstack's platform and APIs. **Tags:** * [goodstack](/changelog/tags/goodstack.md) * [rebrand](/changelog/tags/rebrand.md) [**Read more**](/changelog/goodstack-launch.md) --- ### 2024[​](#2024 "Direct link to 2024") * [October 15](/changelog/goodstack-launch.md) [ - ](/changelog/goodstack-launch.md) [Percent is becoming Goodstack](/changelog/goodstack-launch.md) --- # Authors * [![Stefan Greer](/img/authors/stef.jpeg)](https://uk.linkedin.com/in/stefangreer) ## [Stefan Greer](https://uk.linkedin.com/in/stefangreer) 1 COO --- # Percent is becoming Goodstack October 15, 2024 · One min read [![Stefan Greer](/img/authors/stef.jpeg)](https://uk.linkedin.com/in/stefangreer) [Stefan Greer](https://uk.linkedin.com/in/stefangreer) COO As part of our continued growth in supporting impactful businesses and good causes around the world, we're excited to announce that Percent is rebranding as Goodstack! Goodstack Docs are our new unified documentation hub for developers, impact teams and nonprofits to help you design, build and optimise the tools you need to drive your mission using Goodstack's platform and APIs. info For now you will still see references to Percent in our API URLs and fields. We will be moving everything over to new Goodstack endpoints and updating language throughout our APIs over time, but you'll have plenty of time to migrate. Please reach out to our team if you have any questions about migration timelines and requirements. **Tags:** * [goodstack](/changelog/tags/goodstack.md) * [rebrand](/changelog/tags/rebrand.md) --- ## G[​](#G "Direct link to G") * [goodstack1](/changelog/tags/goodstack.md) *** --- ## [Percent is becoming Goodstack](/changelog/goodstack-launch.md) October 15, 2024 · One min read [![Stefan Greer](/img/authors/stef.jpeg)](https://uk.linkedin.com/in/stefangreer) [Stefan Greer](https://uk.linkedin.com/in/stefangreer) COO As part of our continued growth in supporting impactful businesses and good causes around the world, we're excited to announce that Percent is rebranding as Goodstack! Goodstack Docs are our new unified documentation hub for developers, impact teams and nonprofits to help you design, build and optimise the tools you need to drive your mission using Goodstack's platform and APIs. **Tags:** * [goodstack](/changelog/tags/goodstack.md) * [rebrand](/changelog/tags/rebrand.md) [**Read more**](/changelog/goodstack-launch.md) --- ## [Percent is becoming Goodstack](/changelog/goodstack-launch.md) October 15, 2024 · One min read [![Stefan Greer](/img/authors/stef.jpeg)](https://uk.linkedin.com/in/stefangreer) [Stefan Greer](https://uk.linkedin.com/in/stefangreer) COO As part of our continued growth in supporting impactful businesses and good causes around the world, we're excited to announce that Percent is rebranding as Goodstack! Goodstack Docs are our new unified documentation hub for developers, impact teams and nonprofits to help you design, build and optimise the tools you need to drive your mission using Goodstack's platform and APIs. **Tags:** * [goodstack](/changelog/tags/goodstack.md) * [rebrand](/changelog/tags/rebrand.md) [**Read more**](/changelog/goodstack-launch.md) --- [Skip to main content](#__docusaurus_skipToContent_fallback) [![Goodstack Docs](/img/logo.png)![Goodstack Docs](/img/icons/Logo-white.svg)](/) [Use cases](/docs/use-cases/.md)[Guides](/docs/guides/.md)[Concepts](/docs/concepts/.md)[API Reference](/docs/api/goodstack-services.md) Search # Search the documentation Type your search here Powered by[](https://www.algolia.com/) Products [Verifications](https://goodstack.io/verifications)[Donations](https://goodstack.io/donations)[Workplace giving](https://goodstack.io/workplace-giving) Solutions [Software companies](https://goodstack.io/software-companies)[Banks & loyalty programs](https://goodstack.io/banks)[Social media & platforms](https://goodstack.io/social-media)[Fundraising platforms](https://goodstack.io/fundraising-platforms)[eCommerce](https://goodstack.io/e-commerce) Company [About](https://goodstack.io/about)[Careers](https://goodstack.io/careers)[Contact](https://goodstack.io/get-in-touch)[LinkedIn](https://www.linkedin.com/company/percent)[Legal](https://goodstack.io/legal) Resources [Blog](https://goodstack.io/blog)[DeveloperHub](/)[Changelog](https://changelog.goodstack.io/)[Status](https://goodstack.statuspage.io) ![SOC logo](data:image/webp;base64,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)![ISO logo](data:image/webp;base64,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) © 2026 Goodstack --- # Activities ## [📄️List Activity Tags](/docs/api/list-activity-tags.md) [List Activity Tags](/docs/api/list-activity-tags.md) ## [📄️Retrieve an Activity Tag](/docs/api/retrieve-an-activity-tag.md) [Retrieve an Activity Tag](/docs/api/retrieve-an-activity-tag.md) --- # Add a Organisation to a Category ``` PUT /v1/categories/:categoryId/:organisationId ``` Add a Organisation to a Category ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 * 404 Successfully add an Organisation to a Category Error validating inputs Unauthorized Forbidden The specified resource was not found --- # Agent Verification Events Webhook events triggered during the agent verification process, from pending states to final approval or rejection. ## [📄️Agent verification pending user verification](/docs/api/webhook-agent-verification-pending-user-verification.md) [Agent has passed Goodstack review, and is awaiting email verification.](/docs/api/webhook-agent-verification-pending-user-verification.md) ## [📄️Agent verification pending review](/docs/api/webhook-agent-verification-pending-review.md) [Agent has passed email verification and is awaiting Goodstack review.](/docs/api/webhook-agent-verification-pending-review.md) ## [📄️Agent verification was approved](/docs/api/webhook-agent-verification-approved.md) [Triggered when an agent verification has been approved by Goodstack.](/docs/api/webhook-agent-verification-approved.md) ## [📄️Agent verification was rejected](/docs/api/webhook-agent-verification-rejected.md) [Triggered when an agent verification has been rejected by Goodstack.](/docs/api/webhook-agent-verification-rejected.md) --- # Agent Verifications Agent verification is a one-off check. It is used to confirm that: 1. The person applying is who they say they are. 2. They represent the nonprofit. They are not editable once created. ## [📄️List Agent Verifications](/docs/api/list-agent-verifications.md) [List Agent Verifications](/docs/api/list-agent-verifications.md) ## [📄️Create an Agent Verification](/docs/api/create-an-agent-verification.md) [Create an Agent Verification](/docs/api/create-an-agent-verification.md) ## [📄️Retrieve an Agent Verification](/docs/api/retrieve-an-agent-verification.md) [Retrieve an Agent Verification](/docs/api/retrieve-an-agent-verification.md) ## [📄️Edit an Agent Verification](/docs/api/edit-an-agent-verification.md) [Update an Agent Verifications metadata.](/docs/api/edit-an-agent-verification.md) --- # Apply to a grant program ``` POST /v1/grants/programs/:id/candidate ``` Creates a candidate for an application-type grant program. Needs no credentials. Requests are rate limited per IP address. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 201 * 400 * 404 * 429 Successfully created a candidate for the grant program Bad request The specified resource was not found Too many requests from this IP address **Response Headers** * **Retry-After** Seconds to wait before the next request --- # Budgets ## [📄️List Account Budget state changes](/docs/api/list-account-budget-state-changes.md) [Returns a list of all changes to an account's budget over time. Each entry represents](/docs/api/list-account-budget-state-changes.md) --- # Bulk operations ``` POST /scim/v2/Bulk ``` Apply up to 100 user operations in a single request. Each operation is a `POST`, `PUT`, `PATCH` or `DELETE` against `/Users`. The response is a SCIM BulkResponse; per-operation failures are reported inline and do not fail the whole request unless `failOnErrors` is reached. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 401 * 413 A SCIM bulk response. Unauthorized. The SCIM bearer key is missing or invalid, or the key is valid but the partner does not have the `scim_api` scope enabled. The number of operations exceeds the maximum (100). --- # Campaigns ## [📄️List Campaigns](/docs/api/list-campaigns.md) [Get a paginated list of Campaigns](/docs/api/list-campaigns.md) --- # Cancel a Donation ``` POST /v1/donations/:id/cancel ``` Applicable only to `direct` type Donations created directly through the [Create A Donation API](/docs/api/create-a-donation.md). ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully cancelled a Donation Bad request Unauthorized Forbidden (scope) --- # Cancel a Monitoring Subscription ``` POST /v1/monitoring-subscriptions/:id/cancel ``` Cancel a Monitoring Subscription ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 * 404 Successfully cancelled a Monitoring Subscription Bad request Unauthorized Forbidden (scope) The specified resource was not found --- # Cancel an Eligibility Subscription ``` POST /v1/eligibility-subscriptions/:id/cancel ``` Cancel an Eligibility Subscription ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 * 404 Successfully cancel an Eligibility Subscription Bad request Unauthorized Forbidden (scope) The specified resource was not found --- # Categories Categories represent collections of Organisations, which can be used to group and filter Organisations. ## [📄️List Categories](/docs/api/list-categories.md) [List Categories](/docs/api/list-categories.md) ## [📄️Create a Category](/docs/api/create-a-category.md) [Create a Category](/docs/api/create-a-category.md) ## [📄️Edit a Category](/docs/api/edit-a-category.md) [Edit a Category](/docs/api/edit-a-category.md) ## [📄️Delete a Category](/docs/api/delete-a-category.md) [Delete a Category](/docs/api/delete-a-category.md) ## [📄️Retrieve a Category](/docs/api/retrieve-a-category.md) [Retrieve a Category](/docs/api/retrieve-a-category.md) ## [📄️Add a Organisation to a Category](/docs/api/add-a-organisation-to-a-category.md) [Add a Organisation to a Category](/docs/api/add-a-organisation-to-a-category.md) ## [📄️Remove a Organisation from a Category](/docs/api/remove-a-organisation-from-a-category.md) [Remove a Organisation from a Category](/docs/api/remove-a-organisation-from-a-category.md) ## [📄️Set an Image for a Category](/docs/api/set-an-image-for-a-category.md) [Set an Image for a Category](/docs/api/set-an-image-for-a-category.md) --- # Countries ## [📄️List available Countries](/docs/api/list-available-countries.md) [This endpoint will return all the Countries that Goodstack currently supports. If hasRegistry is true, it will return the subset of Countries that Goodstack currently has nonprofit data for.](/docs/api/list-available-countries.md) --- # Country Compliance Check Events Webhook events triggered when a country compliance check result is stored or re-evaluated for an organisation in your program. ## [📄️Country compliance check result was stored](/docs/api/webhook-country-compliance-check-updated.md) [Triggered when the result of the country compliance check is stored for an organisation in one of](/docs/api/webhook-country-compliance-check-updated.md) --- # Create a Category ``` POST /v1/categories ``` Create a Category ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 Successfully created a Category Error validating inputs Unauthorized --- # Create a donation ``` POST /v1/donations ``` Creates a new Donation of type `direct`. We will request payment from you for the Donations aggregated over a period. Goodstack and our associated foundations will then handle the ongoing safe and efficient disbursement of funds to the nonprofit. Please note that if you are creating with a `userId` and no `organisationId` the user must be already [supporting an organisation](/docs/api/support-organisation.md). ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully created a Donation Bad request Unauthorized Forbidden (scope) --- # Create a Donation Session ``` POST /v1/donation-sessions ``` To create a Donation Session you must supply the `organisationId` of the nonprofit you want to let your user donate to. After creating the Donation Session, direct your user to the URL for the hosted Donation gateway returned in the response. Your user will receive an email receipt after a successful Donation. The status field of the Donation Session object after creation will be `open`. After a successful Donation is made the status will transition to `completed` otherwise if no Donation is completed before the `expiresAt` time the status will be `expired`. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully created a Donation Session Bad request Unauthorized Forbidden (scope) --- # Create a GiftAid Declaration ``` POST /v1/gift-aid ``` Create a GiftAid Declaration ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 Successfully created a GiftAid declaration Bad request Unauthorized --- # Create a Monetary Donation Verification ``` POST /v1/monetary-donation-verifications ``` Creates a new monetary donation verification for a partner. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 201 * 400 * 401 * 403 * 404 Successfully created a Monetary Donation Verification Bad request Unauthorized Forbidden (scope) The specified resource was not found --- # Create a Monitoring Subscription ``` POST /v1/monitoring-subscriptions ``` Create a Monitoring Subscription ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully created a Monitoring Subscription Bad request Unauthorized Forbidden (scope) --- # Create a User ``` POST /v1/users ``` Create a User ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 Successfully created a User Error validating inputs Unauthorized --- # Create a Validation Invite ``` POST /v1/validation-invites ``` Create a Validation Invite ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully created a Validation Invite Bad request Unauthorized Forbidden (scope) --- # Create a Validation Request ``` POST /v1/validation-requests ``` Create a Validation Request ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully created a Validation Request Bad request Unauthorized Forbidden (scope) --- # Create a Validation Request Document ``` POST /v1/validation-requests/:validationRequestId/document ``` Uploads a document and updates the Validation Request to refer to that document. Valid file formats for documents are `jpg`, `png` and `pdf`. The maximum file size is 5MB. `heic`/`heif` photos are accepted too and are converted to `jpg` on upload. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 * 404 Successfully uploaded document and updated Validation Request Bad request Unauthorized Forbidden (scope) The specified resource was not found --- # Create a Validation Submission ``` POST /v1/validation-submissions ``` Create a Validation Submission ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully created a Validation Submission Bad request Unauthorized Forbidden (scope) --- # Create a Webhook Subscription ``` POST /v1/webhook-subscriptions ``` Create a Webhook Subscription. Specify the event types to subscribe to and the URL where the notifications will be sent, the URL must be https. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully created a Webhook Subscription Bad request Unauthorized Forbidden (scope) --- # Create an Agent Verification ``` POST /v1/agent-verifications ``` Create an Agent Verification ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully created a Agent Verification Bad request Unauthorized Forbidden (scope) --- # Create an Eligibility Subscription ``` POST /v1/eligibility-subscriptions ``` Create an Eligibility Subscription ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully created a Eligibility Subscription Bad request Unauthorized Forbidden (scope) --- # Create Validation Submission Document ``` POST /v1/validation-submission-documents ``` Uploads a Validation Submission document. Valid file formats for documents are `jpg`, `png` and `pdf`. The maximum file size is 5MB. `heic`/`heif` photos are accepted too and are converted to `jpg` on upload. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully uploaded Validation Submission document Bad request Unauthorized Forbidden (scope) --- # Deactivate a user ``` DELETE /scim/v2/Users/:id ``` Soft-delete (deactivate) a user. The user must belong to the partner the SCIM key is bound to. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 204 * 401 * 404 The user was deactivated. Unauthorized. The SCIM bearer key is missing or invalid, or the key is valid but the partner does not have the `scim_api` scope enabled. Not found. The user does not exist, or exists but belongs to a different partner than the one the key is bound to. An invalid account id format also resolves to `404`. --- # Delete a Category ``` DELETE /v1/categories/:id ``` Delete a Category ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 204 * 400 * 401 * 403 Successfully deleted a Category Error validating inputs Unauthorized Forbidden --- # Delete a Webhook Subscription ``` DELETE /v1/webhook-subscriptions/:id ``` Delete a Webhook Subscription ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 204 * 401 * 403 * 404 Successfully deleted a Webhook Subscription Unauthorized Forbidden (scope) The specified resource was not found --- # Donation Events Webhook events triggered throughout the donation lifecycle, from payment receipt to final disbursement or cancellation. ## [📄️Payment received for hosted donation](/docs/api/webhook-donation-hosted-payment-received.md) [Payment has been received from the user for a hosted donation.](/docs/api/webhook-donation-hosted-payment-received.md) ## [📄️Donation record created](/docs/api/webhook-donation-created.md) [A new donation record has been successfully created in the system.](/docs/api/webhook-donation-created.md) ## [📄️Donation payment successful](/docs/api/webhook-donation-payment-successful.md) [The payment for the donation has been successfully completed and verified.](/docs/api/webhook-donation-payment-successful.md) ## [📄️Donation settled](/docs/api/webhook-donation-settled.md) [The donated funds have been received and reconciled by the partner foundation.](/docs/api/webhook-donation-settled.md) ## [📄️Donation disbursed](/docs/api/webhook-donation-disbursed.md) [The donated funds have been transferred to the intended nonprofit.](/docs/api/webhook-donation-disbursed.md) ## [📄️Donation cancelled](/docs/api/webhook-donation-cancelled.md) [The donation process has been terminated, either by the donor or due to system issues.](/docs/api/webhook-donation-cancelled.md) ## [📄️Donation reassigned](/docs/api/webhook-donation-reassigned.md) [The 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.](/docs/api/webhook-donation-reassigned.md) --- # Donation Match Request ## [📄️List Donation Match Requests](/docs/api/list-donation-match-requests.md) [Get a paginated list of Donation Match Requests for the authenticated partner.](/docs/api/list-donation-match-requests.md) --- # Donation Sessions ## [📄️List Donation Sessions](/docs/api/list-donation-sessions.md) [List Donation Sessions](/docs/api/list-donation-sessions.md) ## [📄️Create a Donation Session](/docs/api/create-a-donation-session.md) [To create a Donation Session you must supply the \`organisationId\` of the nonprofit you want to let your user donate to.](/docs/api/create-a-donation-session.md) ## [📄️Retrieve donation session](/docs/api/retrieve-donation-session.md) [Retrieve donation session](/docs/api/retrieve-donation-session.md) ## [📄️Update donor details for donation session](/docs/api/update-donor-details-for-donation-session.md) [Updates the email address, name, consent preferences, and/or gift note for any donations linked to the specified donation session.](/docs/api/update-donor-details-for-donation-session.md) --- # Donation Wallets ## [📄️List Donation Wallet state changes](/docs/api/list-donation-wallet-state-changes.md) [Returns a list of all state changes to a donation wallet's balance over time. Each entry](/docs/api/list-donation-wallet-state-changes.md) --- # Donations A Donation object tracks the lifecycle of a Donation from creation and payment to disbursement to the nonprofit. Donations can be created during a Donation Session by the Hosted Donation Gateway or created directly by calling the [Create A Donation API](/docs/api/create-a-donation.md). ## [📄️List Donations](/docs/api/list-donations.md) [Retrieve a list of donations.](/docs/api/list-donations.md) ## [📄️Create a donation](/docs/api/create-a-donation.md) [Creates a new Donation of type \`direct\`. We will request payment from you for the Donations aggregated over a period. Goodstack and our associated foundations will then handle the ongoing safe and efficient disbursement of funds to the nonprofit.](/docs/api/create-a-donation.md) ## [📄️Retrieve a Donation](/docs/api/retrieve-a-donation.md) [Retrieve a donation by its ID.](/docs/api/retrieve-a-donation.md) ## [📄️Edit a Donation](/docs/api/edit-a-donation.md) [Update the recipient organisation, metadata of a Donation. The recipient organisation can only be changed for \`direct\` type Donations created directly through the \[Create A Donation API\](./create-a-donation) that are in an \`ACTIVE\` status.](/docs/api/edit-a-donation.md) ## [📄️Cancel a Donation](/docs/api/cancel-a-donation.md) [Applicable only to \`direct\` type Donations created directly through the \[Create A Donation API\](./create-a-donation).](/docs/api/cancel-a-donation.md) --- # Edit a Category ``` PATCH /v1/categories/:id ``` Edit a Category ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 Successfully edited a Category Error validating inputs Unauthorized --- # Edit a Donation ``` PATCH /v1/donations/:id ``` Update the recipient organisation, metadata of a Donation. The recipient organisation can only be changed for `direct` type Donations created directly through the [Create A Donation API](/docs/api/create-a-donation.md) that are in an `ACTIVE` status. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully edited a particular donation Bad request Unauthorized Forbidden (scope) --- # Edit a Webhook Subscription ``` PATCH /v1/webhook-subscriptions/:id ``` Edit a Webhook Subscription ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 * 404 Successfully edited a Webhook Subscription Bad request Unauthorized Forbidden (scope) The specified resource was not found --- # Edit an Agent Verification ``` PATCH /v1/agent-verifications/:id ``` Update an Agent Verifications metadata. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully update Agent Verification metadata Bad request Unauthorized Forbidden (scope) --- # Edit an Eligibility Subscription ``` PATCH /v1/eligibility-subscriptions/:id ``` Update an Eligibility Subscription's metadata. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 * 404 Successfully updates Eligibility Subscription's metadata Bad request Unauthorized Forbidden (scope) The specified resource was not found --- # Eligibility Subscription Events Webhook events triggered when eligibility subscription status changes, including activity tag confirmations. ## [📄️Eligibility subscription was updated](/docs/api/webhook-eligibility-subscription-updated.md) [Triggered when an eligibility subscription has been updated.](/docs/api/webhook-eligibility-subscription-updated.md) --- # Eligibility Subscriptions ## [📄️List Eligibility Subscriptions](/docs/api/list-eligibility-subscriptions.md) [List Eligibility Subscriptions](/docs/api/list-eligibility-subscriptions.md) ## [📄️Create an Eligibility Subscription](/docs/api/create-an-eligibility-subscription.md) [Create an Eligibility Subscription](/docs/api/create-an-eligibility-subscription.md) ## [📄️Retrieve an single Eligibility Subscription](/docs/api/retrieve-an-single-eligibility-subscription.md) [Retrieve an single Eligibility Subscription](/docs/api/retrieve-an-single-eligibility-subscription.md) ## [📄️Edit an Eligibility Subscription](/docs/api/edit-an-eligibility-subscription.md) [Update an Eligibility Subscription's metadata.](/docs/api/edit-an-eligibility-subscription.md) ## [📄️Cancel an Eligibility Subscription](/docs/api/cancel-an-eligibility-subscription.md) [Cancel an Eligibility Subscription](/docs/api/cancel-an-eligibility-subscription.md) --- # Employees ## [📄️List Workplace Giving Employee Profiles](/docs/api/list-workplace-giving-employee-profiles.md) [List Workplace Giving Employee Profiles](/docs/api/list-workplace-giving-employee-profiles.md) --- # Fundraising ## [📄️List Fundraisers](/docs/api/list-fundraisers.md) [Get a paginated list of Fundraisers](/docs/api/list-fundraisers.md) --- # Gamification ## [📄️List Account Achievements](/docs/api/list-account-achievements.md) [Get a paginated list of Account Achievements](/docs/api/list-account-achievements.md) --- # Get a Monetary Donation Verification by ID ``` GET /v1/monetary-donation-verifications/:id ``` Retrieves a monetary donation verification by its ID for the authenticated partner. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 * 404 Successfully retrieved the Monetary Donation Verification Bad request Unauthorized Forbidden (scope) The specified resource was not found --- # Get organisation branding ``` GET /v1/branding/organisations/:organisationId ``` Retrieve branding information for a specific organisation ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 * 404 Successfully retrieved organisation branding Bad request Unauthorized Forbidden The specified resource was not found --- # List candidates for a grant program ``` GET /v1/grants/data/programs/:programId/candidates ``` Fetches paginated candidate data for a grant program, enriched with organisation details, state transitions, and form submissions. Requires partner secret key authentication and the Grants scope. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 * 404 Successfully retrieved candidates Bad request Unauthorized Forbidden (scope) The specified resource was not found --- # List form questions for a grant program ``` GET /v1/grants/programs/:id/form-questions ``` Returns the form questions of each task template in the grant program, grouped by task template. Document questions are not included. Task templates without questions are not included. Task templates with the external audience are last. Requires the Grants scope and the grants:read permission. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully retrieved the form questions Bad request Unauthorized Forbidden (scope) --- # List task assignees for the partner ``` GET /v1/grants/task-assignees ``` Returns the accounts that the task auto-assignment rules of the partner assign tasks to. Requires the Grants scope and the grants:read permission. ## Responses[​](#responses "Direct link to Responses") * 200 * 401 * 403 Successfully retrieved the task assignees Unauthorized Forbidden (scope) --- # Get Validation Submission Documents ``` GET /v1/validation-submissions/:id/documents ``` Get Validation Submission Documents ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 401 * 403 * 404 Successfully retrieved list of Validation Submissions Documents Unauthorized Forbidden (scope) The specified resource was not found --- # Gift Aid ## [📄️Create a GiftAid Declaration](/docs/api/create-a-gift-aid-declaration.md) [Create a GiftAid Declaration](/docs/api/create-a-gift-aid-declaration.md) ## [📄️Retrieve GiftAid id for User](/docs/api/retrieve-gift-aid-id-for-user.md) [Returns the most recent gift aid id for a given user, returns 404 not found if no gift aid exists.](/docs/api/retrieve-gift-aid-id-for-user.md) --- # Giving Donations ## [📄️List Donations](/docs/api/list-giving-donations.md) [Retrieve a paginated list of donations with optional filtering and organisation expansion.](/docs/api/list-giving-donations.md) --- Version: v1 Export * [OpenAPI Spec](https://reference.poweredbypercent.com/openapi.json) # Goodstack Services ![Goodstack logo](https://docs.goodstack.io/img/logo.png)![Goodstack logo](https://docs.goodstack.io/img/icons/Logo-white.svg) # 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 ## IDs[​](#ids "Direct link to IDs") All ID fields are case-sensitive. For example, `abc123` and `ABC123` are treated as different IDs. ## Dates[​](#dates "Direct link to Dates") Dates are encoded as strings following the ISO 8601 standard `2025-08-01T12:00:00.000Z`. ## Pagination[​](#pagination "Direct link to 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. A `_links` object is included in paginated responses, which contains `next` and `prev` URLs to retrieve the next or previous pages of results. ## Monetary Values[​](#monetary-values "Direct link to Monetary Values") Throughout the API, monetary values are encoded as integers representing the amount in the smallest currency unit, for example 100 cents for a donation of $1.00. Currency is encoded as a string following the ISO 4217 standard (e.g. `USD`). ## Metadata[​](#metadata "Direct link to 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[​](#errors "Direct link to 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. | Code | Title | Description | | ---- | --------------------- | ------------------------------------------- | | 200 | Success | Request successful | | 204 | No Content | Request successful with no content returned | | 400 | Bad Request | Request was invalid | | 401 | Unauthorised | Authorization header is invalid | | 403 | Forbidden | Insufficient permissions | | 404 | Not Found | Resource does not exist | | 429 | Too Many Requests | Rate Limited | | 500s | Internal Server Error | Something went wrong with the Goodstack API | # Enum Values Generally enum fields in our API are open-ended. New values may be added to an enum and this is not considered a breaking change. ## Integrating safely[​](#integrating-safely "Direct link to Integrating safely") If your code handles enum values exhaustively — for example, a switch without a default case, or deserialisation into a strict typed enum — an unrecognised value may cause errors. We recommend adding a fallback handler for unknown enum values, so that any new value that does reach your integration degrades gracefully rather than failing. Some endpoints that return enum fields support filtering by those values as query parameters, letting you restrict responses to the values your integration supports. # 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](/docs/api/webhook-subscriptions.md). For detailed webhook event specifications, see the webhook events section. ## Webhook Structure[​](#webhook-structure "Direct link to Webhook Structure") All webhook payloads contain `object` and `data` fields. The data record contains: | Parameter | Type | Description | | --------- | ------ | -------------------------------------------------------- | | id | string | Id of the event | | createdAt | string | Timestamp of when the event was created | | eventType | string | The type of the event e.g. `validation_request.approved` | | eventData | record | Data associated with the event | ## Webhook Security & Delivery[​](#webhook-security--delivery "Direct link to Webhook Security & Delivery") **IP Addresses:** Webhook notifications come from these IP addresses: Production: `54.228.234.204`, `54.76.67.168`, `34.248.188.89`, `34.243.152.218` Sandbox: `79.125.45.124`, `54.76.168.240`, `99.81.243.145`, `54.220.118.167` **Response Requirements:** Your endpoint must return a 2xx HTTP status code to acknowledge receipt. **Retry Policy:** Failed webhooks are retried 4 times over 14 hours with increasing delays. **Verification:** Each webhook includes a `Goodstack-Signature` header generated using HMAC SHA-256 with your webhook subscription secret as the key. **Idempotency:** Use the event `id` to handle duplicate webhook deliveries idempotently. ## Authentication[​](#authentication "Direct link to Authentication") * API Key: PublishableApiKey * API Key: SecretApiKey * API Key: DonationSessionToken * HTTP: Bearer Auth API key starting with 'pk\_' that can be made public | Security Scheme Type: | apiKey | | ---------------------- | ------------- | | Header parameter name: | Authorization | API key starting with 'sk\_' that must be kept secure | Security Scheme Type: | apiKey | | ---------------------- | ------------- | | Header parameter name: | Authorization | Donation session token | Security Scheme Type: | apiKey | | ---------------------- | ---------------------- | | Header parameter name: | donation-session-token | SCIM API key issued for the partner, presented as a bearer token in the `Authorization` header. The key is bound to a single partner and the partner must have the `scim_api` scope enabled. A missing or invalid key returns `401`; a valid key for a partner without the scope also returns `401` (`detail: "scim feature not enabled for this partner"`). | Security Scheme Type: | http | | -------------------------- | ------ | | HTTP Authorization Scheme: | bearer | ### Contact Api support: ## Validation submission events[​](#validation-submission-events "Direct link to Validation submission events") See the [Validation submission events](/docs/api/validation-submission-events.md) reference. ## Donation events[​](#donation-events "Direct link to Donation events") See the [Donation events](/docs/api/donation-events.md) reference. --- # Grants Grant programs and their candidates. ## [📄️Apply to a grant program](/docs/api/apply-to-a-grant-program.md) [Creates a candidate for an application-type grant program. Needs no credentials. Requests are rate limited per IP address.](/docs/api/apply-to-a-grant-program.md) ## [📄️List candidates for a grant program](/docs/api/get-program-candidates.md) [Fetches paginated candidate data for a grant program, enriched with organisation details, state transitions, and form submissions. Requires partner secret key authentication and the Grants scope.](/docs/api/get-program-candidates.md) ## [📄️List form questions for a grant program](/docs/api/get-program-form-questions.md) [Returns the form questions of each task template in the grant program, grouped by task template. Document questions are not included. Task templates without questions are not included. Task templates with the external audience are last. Requires the Grants scope and the grants:read permission.](/docs/api/get-program-form-questions.md) ## [📄️List task assignees for the partner](/docs/api/get-task-assignees.md) [Returns the accounts that the task auto-assignment rules of the partner assign tasks to. Requires the Grants scope and the grants:read permission.](/docs/api/get-task-assignees.md) --- # List Account Achievements ``` GET /v1/gamification/account-achievements ``` Get a paginated list of Account Achievements ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 401 * 403 Successfully retrieved Account Achievements Unauthorized Forbidden (scope) --- # List Account Budget state changes ``` GET /v1/giving/account-budgets/changes ``` Returns a list of all changes to an account's budget over time. Each entry represents the resulting budget state after a change occurred, along with the type of change that caused the update. The type of change is indicated by the type field. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 401 * 403 Successfully retrieved Account Budget changes Unauthorized Forbidden (scope) --- # List Activity Tags ``` GET /v1/activity-tags ``` List Activity Tags ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 Successfully retrieved Error validating inputs Unauthorized --- # List Agent Verifications ``` GET /v1/agent-verifications ``` List Agent Verifications ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully retrieved list of Agent Verifications Bad request Unauthorized Forbidden (scope) --- # List an Organisations Milestones ``` GET /v1/organisations/:id/milestones ``` List an Organisations Milestones ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 401 * 403 Successfully list an Organisations milestones Unauthorized Forbidden (scope) --- # List available Countries ``` GET /v1/countries ``` This endpoint will return all the Countries that Goodstack currently supports. If hasRegistry is true, it will return the subset of Countries that Goodstack currently has nonprofit data for. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully retrieved Countries Error validating inputs Unauthorized Forbidden (scope) --- # List Campaigns ``` GET /v1/giving/campaigns ``` Get a paginated list of Campaigns ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully retrieved Giving Campaigns Error validating inputs Unauthorized Forbidden (scope) --- # List Categories ``` GET /v1/categories ``` List Categories ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 Successfully retrieved a list of Categories Bad request Unauthorized --- # List Donation Match Requests ``` GET /v1/donation-match-requests ``` Get a paginated list of Donation Match Requests for the authenticated partner. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully retrieved a list of Donation Match Requests Bad request Unauthorized Forbidden (scope) --- # List Donation Sessions ``` GET /v1/donation-sessions ``` List Donation Sessions ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully lists Donation Sessions Bad request Unauthorized Forbidden (scope) --- # List Donation Wallet state changes ``` GET /v1/donation-wallets/changes ``` Returns a list of all state changes to a donation wallet's balance over time. Each entry represents the resulting wallet balance after a change occurred, along with the type of change that caused the update. The type of change is indicated by the type field. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 401 * 403 Successfully retrieved Donation Wallet state changes Unauthorized Forbidden (scope) --- # List Donations ``` GET /v1/donations ``` Retrieve a list of donations. PII fields (`firstName`, `lastName` & `email`) will be included or excluded depending on your partner settings. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully listed Donations Bad request Unauthorized Forbidden (scope) --- # List Eligibility Subscriptions ``` GET /v1/eligibility-subscriptions ``` List Eligibility Subscriptions ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully retrieved a page of Eligibility Subscriptions Bad request Unauthorized Forbidden (scope) --- # List Fundraisers ``` GET /v1/fundraisers ``` Get a paginated list of Fundraisers ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully retrieved a page of Fundraisers Error validating inputs Unauthorized Forbidden (scope) --- # List Donations ``` GET /v1/giving/donations ``` Retrieve a paginated list of donations with optional filtering and organisation expansion. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully listed Donations Error validating inputs Unauthorized Forbidden (scope) --- # List Monitoring Subscriptions ``` GET /v1/monitoring-subscriptions ``` List Monitoring Subscriptions ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully retrieved a page of Monitoring Subscriptions Bad request Unauthorized Forbidden (scope) --- # List Registries ``` GET /v1/registries ``` List Registries ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully retrieved Registries for a Country Error validating inputs Unauthorized Forbidden (scope) --- # List resource types ``` GET /scim/v2/ResourceTypes ``` Discovery endpoint listing the supported resource types. The `User` resource type advertises the Enterprise User and Goodstack User extensions under `schemaExtensions`. ## Responses[​](#responses "Direct link to Responses") * 200 * 401 A SCIM list of resource types. Unauthorized. The SCIM bearer key is missing or invalid, or the key is valid but the partner does not have the `scim_api` scope enabled. --- # List supported schemas ``` GET /scim/v2/Schemas ``` Discovery endpoint listing the supported SCIM schemas: the Core User, the Enterprise User extension and the Goodstack User extension. Truncated attributes are excluded. ## Responses[​](#responses "Direct link to Responses") * 200 * 401 A SCIM list of schema definitions. Unauthorized. The SCIM bearer key is missing or invalid, or the key is valid but the partner does not have the `scim_api` scope enabled. --- # List users ``` GET /scim/v2/Users ``` Return a paginated, optionally filtered and sorted list of users that belong to the partner the SCIM key is bound to. The response is a SCIM ListResponse envelope. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 A SCIM list of users. Bad request. Input validation failed or a filter could not be parsed. Unauthorized. The SCIM bearer key is missing or invalid, or the key is valid but the partner does not have the `scim_api` scope enabled. --- # List Validation Requests ``` GET /v1/validation-requests ``` List Validation Requests ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully listed Validation Requests Bad request Unauthorized Forbidden (scope) --- # List Validation Submissions ``` GET /v1/validation-submissions ``` List Validation Submissions ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully retrieved list of Validation Submissions Bad request Unauthorized Forbidden (scope) --- # List Volunteering Opportunities ``` GET /v1/volunteering/opportunities ``` Get a paginated list of Volunteering Opportunities ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully retrieved a page of Opportunities Error validating inputs Unauthorized Forbidden (scope) --- # List Volunteering Opportunity Participants ``` GET /v1/volunteering/opportunity-participants ``` Get a paginated list of Volunteering Opportunity Participants ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 401 * 403 Successfully retrieved a list of opportunity participants Unauthorized Forbidden (scope) --- # List Volunteering Time Logs ``` GET /v1/volunteering/time-logs ``` Get a paginated list of Volunteering Time Logs ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 401 * 403 Successfully retrieved a list of time logs Unauthorized Forbidden (scope) --- # List Webhook Subscriptions ``` GET /v1/webhook-subscriptions ``` List Webhook Subscriptions ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully lists Webhook Subscriptions Bad request Unauthorized Forbidden (scope) --- # List Workplace Giving Employee Profiles ``` GET /v1/giving/profiles ``` List Workplace Giving Employee Profiles ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully retrieved a list of Workplace Giving Employees Error validating inputs Unauthorized Forbidden (scope) --- # Monetary Donation Verification Events Webhook events triggered during the monetary donation verification lifecycle, from receiving a new verification to status changes as checks progress. ## [📄️Monetary donation verification received](/docs/api/webhook-monetary-donation-verification-received.md) [Triggered when a new monetary donation verification has been received and created.](/docs/api/webhook-monetary-donation-verification-received.md) ## [📄️Monetary donation verification progressed](/docs/api/webhook-monetary-donation-verification-progressed.md) [Triggered when a monetary donation verification has progressed, such as a check succeeding, failing, the verification being approved, rejected, expired, or reopened.](/docs/api/webhook-monetary-donation-verification-progressed.md) --- # Monetary Donation Verifications Monetary Donation Verification is a check used to confirm whether a given organisation is eligible to receive monetary donations. ## [📄️Create a Monetary Donation Verification](/docs/api/create-a-monetary-donation-verification.md) [Creates a new monetary donation verification for a partner.](/docs/api/create-a-monetary-donation-verification.md) ## [📄️Get a Monetary Donation Verification by ID](/docs/api/get-a-monetary-donation-verification-by-id.md) [Retrieves a monetary donation verification by its ID for the authenticated partner.](/docs/api/get-a-monetary-donation-verification-by-id.md) --- # Monitoring Subscription Events Webhook events triggered when monitoring subscription status changes, including compliance check results. ## [📄️Monitoring subscription was updated](/docs/api/webhook-monitoring-subscription-updated.md) [Triggered when a monitoring subscription has been updated.](/docs/api/webhook-monitoring-subscription-updated.md) --- # Monitoring Subscriptions ## [📄️List Monitoring Subscriptions](/docs/api/list-monitoring-subscriptions.md) [List Monitoring Subscriptions](/docs/api/list-monitoring-subscriptions.md) ## [📄️Create a Monitoring Subscription](/docs/api/create-a-monitoring-subscription.md) [Create a Monitoring Subscription](/docs/api/create-a-monitoring-subscription.md) ## [📄️Retrieve a Monitoring Subscription](/docs/api/retrieve-a-monitoring-subscription.md) [Retrieve a Monitoring Subscription](/docs/api/retrieve-a-monitoring-subscription.md) ## [📄️Cancel a Monitoring Subscription](/docs/api/cancel-a-monitoring-subscription.md) [Cancel a Monitoring Subscription](/docs/api/cancel-a-monitoring-subscription.md) --- # Org name search ## [📄️Search for Validation Submissions with specific org name](/docs/api/search-for-validation-submissions-with-specific-org-name.md) [Search for Validation Submissions with specific org name](/docs/api/search-for-validation-submissions-with-specific-org-name.md) --- # Organisation ## [📄️Add a Organisation to a Category](/docs/api/add-a-organisation-to-a-category.md) [Add a Organisation to a Category](/docs/api/add-a-organisation-to-a-category.md) ## [📄️Remove a Organisation from a Category](/docs/api/remove-a-organisation-from-a-category.md) [Remove a Organisation from a Category](/docs/api/remove-a-organisation-from-a-category.md) --- # Organisation Branding ## [📄️Get organisation branding](/docs/api/get-organisation-branding.md) [Retrieve branding information for a specific organisation](/docs/api/get-organisation-branding.md) --- # Organisations Nonprofits are represented by the Organisation object. You can retrieve and query for Organisations. ## [📄️Search Organisations](/docs/api/search-organisations.md) [Search for nonprofits. The Organizations Search API provides up to 1,000 results for each search query, accessible via pagination.](/docs/api/search-organisations.md) ## [📄️Retrieve an Organisation](/docs/api/retrieve-an-organisation.md) [Retrieve an Organisation](/docs/api/retrieve-an-organisation.md) ## [📄️List an Organisations Milestones](/docs/api/list-an-organisations-milestones.md) [List an Organisations Milestones](/docs/api/list-an-organisations-milestones.md) ## [📄️Retrieve an Organisation by country code, registry and registryId](/docs/api/retrieve-an-organisation-by-country-code-registry-and-registry-id.md) [Retrieve an Organisation by country code, registry and registryId](/docs/api/retrieve-an-organisation-by-country-code-registry-and-registry-id.md) ## [📄️Retrieve an Organisation profile info](/docs/api/retrieve-an-organisation-profile-info.md) [Retrieve an Organisation profile info](/docs/api/retrieve-an-organisation-profile-info.md) --- # Registries ## [📄️List Registries](/docs/api/list-registries.md) [List Registries](/docs/api/list-registries.md) ## [📄️Retrieve a Registry](/docs/api/retrieve-a-registry.md) [Retrieve a Registry](/docs/api/retrieve-a-registry.md) --- # Remove a Organisation from a Category ``` DELETE /v1/categories/:categoryId/:organisationId ``` Remove a Organisation from a Category ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 204 * 400 * 401 * 403 * 404 Successfully remove a Organisation from a Category Error validating inputs Unauthorized Forbidden The specified resource was not found --- # Replace a user ``` PUT /scim/v2/Users/:id ``` Replace a user's attributes. Attributes omitted from the request are cleared. `userName` (email) and other fields are validated; an invalid email returns `400`. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 404 * 409 The updated user. Bad request. Input validation failed or a filter could not be parsed. Unauthorized. The SCIM bearer key is missing or invalid, or the key is valid but the partner does not have the `scim_api` scope enabled. Not found. The user does not exist, or exists but belongs to a different partner than the one the key is bound to. An invalid account id format also resolves to `404`. Conflict. A user with this `userName` already exists for the partner. --- # Retrieve a Category ``` GET /v1/categories/:id ``` Retrieve a Category ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 * 404 Successfully retrieved a Category Error validating inputs Unauthorized Forbidden The specified resource was not found --- # Retrieve a Donation ``` GET /v1/donations/:id ``` Retrieve a donation by its ID. PII fields (`firstName`, `lastName` & `email`) will be included or excluded depending on your partner settings. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 401 * 403 * 404 Successfully retrieves a Donation Unauthorized Forbidden (scope) The specified resource was not found --- # Retrieve a Monitoring Subscription ``` GET /v1/monitoring-subscriptions/:id ``` Retrieve a Monitoring Subscription ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 * 404 Successfully retrieved a Monitoring Subscription Error validating inputs Unauthorized Forbidden (scope) The specified resource was not found --- # Retrieve a Registry ``` GET /v1/registries/:id ``` Retrieve a Registry ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 401 * 403 * 404 Successfully retrieved a Registry Unauthorized Forbidden (scope) The specified resource was not found --- # Retrieve a schema ``` GET /scim/v2/Schemas/:id ``` Retrieve a single schema definition by its URN. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 401 * 404 A SCIM schema definition. Unauthorized. The SCIM bearer key is missing or invalid, or the key is valid but the partner does not have the `scim_api` scope enabled. Not found. The user does not exist, or exists but belongs to a different partner than the one the key is bound to. An invalid account id format also resolves to `404`. --- # Retrieve a User ``` GET /v1/users/:id ``` Retrieve a User ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 Successfully retrieved a User Error validating inputs Unauthorized --- # Retrieve a Validation Request ``` GET /v1/validation-requests/:id ``` Retrieve a Validation Request ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 * 404 Successfully retrieved a Validation Request Error validating inputs Unauthorized Forbidden (scope) The specified resource was not found --- # Retrieve a Validation Submission ``` GET /v1/validation-submissions/:id ``` Retrieve a Validation Submission ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 401 * 403 * 404 Successfully retrieves a Validation Submission Unauthorized Forbidden (scope) The specified resource was not found --- # Retrieve a Webhook Subscription ``` GET /v1/webhook-subscriptions/:id ``` Retrieve a Webhook Subscription ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 401 * 403 * 404 Successfully retrieves a Webhook Subscription Unauthorized Forbidden (scope) The specified resource was not found --- # Retrieve an Activity Tag ``` GET /v1/activity-tags/:tagId ``` Retrieve an Activity Tag ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 404 Retrieve an Activity tag The specified resource was not found --- # Retrieve an Agent Verification ``` GET /v1/agent-verifications/:id ``` Retrieve an Agent Verification ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 * 404 Successfully retrieved a Agent Verification Error validating inputs Unauthorized Forbidden (scope) The specified resource was not found --- # Retrieve an Organisation ``` GET /v1/organisations/:id ``` Retrieve an Organisation ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 401 * 403 * 404 Retrieve a nonprofit Organisation Unauthorized Forbidden (scope) The specified resource was not found --- # Retrieve an Organisation by country code, registry and registryId ``` GET /v1/organisations/:countryCode/:registry/:registryId ``` Retrieve an Organisation by country code, registry and registryId ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 401 * 403 * 404 Retrieve a Organisation by country code, registry and registryId Unauthorized Forbidden (scope) The specified resource was not found --- # Retrieve an Organisation profile info ``` GET /v1/organisations/:id/profile ``` Retrieve an Organisation profile info ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 404 Retrieve a nonprofit Organisation profile The specified resource was not found --- # Retrieve an single Eligibility Subscription ``` GET /v1/eligibility-subscriptions/:id ``` Retrieve an single Eligibility Subscription ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 * 404 Successfully retrieved an Eligibility Subscription Error validating inputs Unauthorized Forbidden (scope) The specified resource was not found --- # Retrieve donation session ``` GET /v1/donation-sessions/:id ``` Retrieve donation session ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 * 404 Successfully retrieved a donation session Bad request Unauthorized Forbidden (scope) The specified resource was not found --- # Retrieve GiftAid id for User ``` GET /v1/users/:id/has-gift-aid ``` Returns the most recent gift aid id for a given user, returns 404 not found if no gift aid exists. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 401 * 404 Successfully retrieved GiftAid id for User Unauthorized The specified resource was not found --- # Retrieve partner's configurations ``` GET /v1/validation-submission-configurations ``` Retrieve partner's configurations ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully fetch configurations Bad request Unauthorized Forbidden (scope) --- # Retrieve service provider configuration ``` GET /scim/v2/ServiceProviderConfig ``` Discovery endpoint describing the SCIM features supported by the service provider (patch, bulk, filter, sort, change password, etag and authentication schemes). ## Responses[​](#responses "Direct link to Responses") * 200 * 401 The SCIM service provider configuration. Unauthorized. The SCIM bearer key is missing or invalid, or the key is valid but the partner does not have the `scim_api` scope enabled. --- # Retrieve specific configuration ``` GET /v1/validation-submission-configurations/:id ``` Retrieve specific configuration ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 401 * 403 * 404 Successfully fetch configurations Unauthorized Forbidden (scope) The specified resource was not found --- # SCIM SCIM 2.0 compliant API for provisioning and managing users. All `/scim/v2` endpoints authenticate with a partner SCIM API key as a bearer token and require the partner's `scim_api` scope. Responses use the `application/scim+json` media type. ## [📄️List users](/docs/api/list-users.md) [Return a paginated, optionally filtered and sorted list of users that](/docs/api/list-users.md) ## [📄️Create a user](/docs/api/scim-create-user.md) [Provision a new user for the partner the SCIM key is bound to.](/docs/api/scim-create-user.md) ## [📄️Retrieve a user](/docs/api/scim-retrieve-user.md) [Retrieve a single user by id. The user must belong to the partner the SCIM key is bound to.](/docs/api/scim-retrieve-user.md) ## [📄️Replace a user](/docs/api/replace-a-user.md) [Replace a user's attributes. Attributes omitted from the request are](/docs/api/replace-a-user.md) ## [📄️Update a user](/docs/api/update-a-user.md) [Apply a SCIM PATCH operation set to a user.](/docs/api/update-a-user.md) ## [📄️Deactivate a user](/docs/api/deactivate-a-user.md) [Soft-delete (deactivate) a user. The user must belong to the partner the SCIM key is bound to.](/docs/api/deactivate-a-user.md) ## [📄️Bulk operations](/docs/api/bulk-operations.md) [Apply up to 100 user operations in a single request. Each operation is a](/docs/api/bulk-operations.md) ## [📄️List supported schemas](/docs/api/list-supported-schemas.md) [Discovery endpoint listing the supported SCIM schemas: the Core User,](/docs/api/list-supported-schemas.md) ## [📄️Retrieve a schema](/docs/api/retrieve-a-schema.md) [Retrieve a single schema definition by its URN.](/docs/api/retrieve-a-schema.md) ## [📄️List resource types](/docs/api/list-resource-types.md) [Discovery endpoint listing the supported resource types. The \`User\`](/docs/api/list-resource-types.md) ## [📄️Retrieve service provider configuration](/docs/api/retrieve-service-provider-configuration.md) [Discovery endpoint describing the SCIM features supported by the service](/docs/api/retrieve-service-provider-configuration.md) --- # Create a user ``` POST /scim/v2/Users ``` Provision a new user for the partner the SCIM key is bound to. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 201 * 400 * 401 * 409 The created user. Bad request. Input validation failed or a filter could not be parsed. Unauthorized. The SCIM bearer key is missing or invalid, or the key is valid but the partner does not have the `scim_api` scope enabled. Conflict. A user with this `userName` already exists for the partner. --- # Retrieve a user ``` GET /scim/v2/Users/:id ``` Retrieve a single user by id. The user must belong to the partner the SCIM key is bound to. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 401 * 404 The requested user. Unauthorized. The SCIM bearer key is missing or invalid, or the key is valid but the partner does not have the `scim_api` scope enabled. Not found. The user does not exist, or exists but belongs to a different partner than the one the key is bound to. An invalid account id format also resolves to `404`. --- # Search for Validation Submissions with specific org name ``` GET /v1/validation-submissions-organisations ``` Search for Validation Submissions with specific org name ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 Successfully retrieved results Error validating inputs Unauthorized --- # Search Organisations ``` GET /v1/organisations ``` Search for nonprofits. The Organizations Search API provides up to 1,000 results for each search query, accessible via pagination. The exhaustiveTotalResults boolean field indicates whether the value of totalResults is exact (true) or approximate (false). If the system determines that the total number of results exceeds 10,000, the exhaustiveTotalResults field is set to false, indicating that totalResults is an approximation and that there are likely more than 10,000 results matching the query. If lat/lon coordinates are provided, the top results will be within the specified distance from this location, ranked by closeness. If lat/lon is provided without specifying a distance, a default of 100km will be used. Results outside of this radius this will be ranked by name match without consideration of distance. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully retrieved a page of organisations Error validating inputs Unauthorized Forbidden (scope) --- # Set an Image for a Category ``` POST /v1/categories/:categoryId/image ``` Set an Image for a Category ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 Successfully add an image to a Category Bad request Unauthorized Forbidden --- # Set User to support an Organisation ``` PUT /v1/users/:id/organisation ``` For Donations created through the [Create A Donation API](/docs/api/create-a-donation.md), A User can be set to donate to a particular Organisation by default using this API. If a Donation is made with a userId, the Donation will be associated with the Organisation the User is supporting at the time of Donation. If the userId is omitted or the User has not been set to donate to an Organisation by default then the organisationId must be included in the body. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 Successfully set User to support an Organisation Bad request Unauthorized --- # Update a user ``` PATCH /scim/v2/Users/:id ``` Apply a SCIM PATCH operation set to a user. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 404 The updated user. Bad request. Input validation failed or a filter could not be parsed. Unauthorized. The SCIM bearer key is missing or invalid, or the key is valid but the partner does not have the `scim_api` scope enabled. Not found. The user does not exist, or exists but belongs to a different partner than the one the key is bound to. An invalid account id format also resolves to `404`. --- # Update donor details for donation session ``` PATCH /v1/donation-sessions/:sessionId/donations ``` Updates the email address, name, consent preferences, and/or gift note for any donations linked to the specified donation session. This endpoint requires both a public API key and a valid donation session token in the headers. At least one field (email, firstName, lastName, consentedToBeContactedByOrg, or giftNote) must be provided in the request body. If no donation is linked to the session, the endpoint will return success without making any changes. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 * 401 * 403 * 404 Successfully updated donor details Bad request Unauthorized Forbidden - missing donation session token or invalid scope The specified resource was not found --- # Users The User object represents a user of your application. It can be used to track Donations that belong to that user. ## [📄️Create a User](/docs/api/create-a-user.md) [Create a User](/docs/api/create-a-user.md) ## [📄️Retrieve a User](/docs/api/retrieve-a-user.md) [Retrieve a User](/docs/api/retrieve-a-user.md) ## [📄️Set User to support an Organisation](/docs/api/support-organisation.md) [For Donations created through the \[Create A Donation API\](./create-a-donation), A User can be set to donate to a particular Organisation by default using this API. If a Donation is made with a userId, the Donation will be associated with the Organisation the User is supporting at the time of Donation. If the userId is omitted or the User has not been set to donate to an Organisation by default then the organisationId must be included in the body.](/docs/api/support-organisation.md) --- # Validation Invites ## [📄️Create a Validation Invite](/docs/api/create-a-validation-invite.md) [Create a Validation Invite](/docs/api/create-a-validation-invite.md) --- # Validation Request Documents ## [📄️Create a Validation Request Document](/docs/api/create-a-validation-request-document.md) [Uploads a document and updates the Validation Request to refer to that document. Valid file formats for documents are \`jpg\`, \`png\` and \`pdf\`. The maximum file size is 5MB. \`heic\`/\`heif\` photos are accepted too and are converted to \`jpg\` on upload.](/docs/api/create-a-validation-request-document.md) --- # Validation Request Events Webhook events triggered when validation requests are approved or rejected by Goodstack. ## [📄️Validation request was approved](/docs/api/webhook-validation-request-approved.md) [Triggered when a validation request has been approved by Goodstack.](/docs/api/webhook-validation-request-approved.md) ## [📄️Validation request was rejected](/docs/api/webhook-validation-request-rejected.md) [Triggered when a validation request has been rejected by Goodstack.](/docs/api/webhook-validation-request-rejected.md) --- # Validation Requests If you want to know if a nonprofit exists in an official registry, create a Validation Request and we will check it for you. Documentary evidence can be attached to a Validation Request. When a Validation Request is accepted or rejected, the appropriate field (acceptedAt, rejectedAt) will be populated. ## [📄️List Validation Requests](/docs/api/list-validation-requests.md) [List Validation Requests](/docs/api/list-validation-requests.md) ## [📄️Create a Validation Request](/docs/api/create-a-validation-request.md) [Create a Validation Request](/docs/api/create-a-validation-request.md) ## [📄️Retrieve a Validation Request](/docs/api/retrieve-a-validation-request.md) [Retrieve a Validation Request](/docs/api/retrieve-a-validation-request.md) --- # Validation Submission Configuration ## [📄️Retrieve partner's configurations](/docs/api/retrieve-partners-configurations.md) [Retrieve partner's configurations](/docs/api/retrieve-partners-configurations.md) ## [📄️Retrieve specific configuration](/docs/api/retrieve-specific-configuration.md) [Retrieve specific configuration](/docs/api/retrieve-specific-configuration.md) --- # Validation Submission Documents ## [📄️Create Validation Submission Document](/docs/api/create-validation-submission-document.md) [Uploads a Validation Submission document. Valid file formats for documents are \`jpg\`, \`png\` and \`pdf\`. The maximum file size is 5MB. \`heic\`/\`heif\` photos are accepted too and are converted to \`jpg\` on upload.](/docs/api/create-validation-submission-document.md) ## [📄️Get Validation Submission Documents](/docs/api/get-validation-submission-documents.md) [Get Validation Submission Documents](/docs/api/get-validation-submission-documents.md) --- # Validation Submission Events Webhook events triggered throughout the validation submission lifecycle, from creation to completion or failure. ## [📄️Validation submission was created](/docs/api/webhook-validation-submission-created.md) [Triggered when a validation submission has been created.](/docs/api/webhook-validation-submission-created.md) ## [📄️Validation submission has passed all checks](/docs/api/webhook-validation-submission-succeeded.md) [Triggered when a validation submission has successfully passed all validation checks.](/docs/api/webhook-validation-submission-succeeded.md) ## [📄️Validation submission has failed](/docs/api/webhook-validation-submission-failed.md) [Triggered when a validation submission has failed validation checks.](/docs/api/webhook-validation-submission-failed.md) ## [📄️Validation submission status has updated](/docs/api/webhook-validation-submission-updated.md) [Triggered when a validation submission status has been updated.](/docs/api/webhook-validation-submission-updated.md) --- # Validation Submissions ## [📄️List Validation Submissions](/docs/api/list-validation-submissions.md) [List Validation Submissions](/docs/api/list-validation-submissions.md) ## [📄️Create a Validation Submission](/docs/api/create-a-validation-submission.md) [Create a Validation Submission](/docs/api/create-a-validation-submission.md) ## [📄️Retrieve a Validation Submission](/docs/api/retrieve-a-validation-submission.md) [Retrieve a Validation Submission](/docs/api/retrieve-a-validation-submission.md) --- # Volunteering ## [📄️List Volunteering Opportunities](/docs/api/list-volunteering-opportunities.md) [Get a paginated list of Volunteering Opportunities](/docs/api/list-volunteering-opportunities.md) ## [📄️List Volunteering Opportunity Participants](/docs/api/list-volunteering-opportunity-participants.md) [Get a paginated list of Volunteering Opportunity Participants](/docs/api/list-volunteering-opportunity-participants.md) ## [📄️List Volunteering Time Logs](/docs/api/list-volunteering-time-logs.md) [Get a paginated list of Volunteering Time Logs](/docs/api/list-volunteering-time-logs.md) --- # Agent verification was approved ``` POST /webhooks/agent-verification-approved ``` Triggered when an agent verification has been approved by Goodstack. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 Webhook received successfully Invalid webhook payload or signature verification failed --- # Agent verification pending review ``` POST /webhooks/agent-verification-pending-review ``` Agent has passed email verification and is awaiting Goodstack review. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 Webhook received successfully Invalid webhook payload or signature verification failed --- # Agent verification pending user verification ``` POST /webhooks/agent-verification-pending-user-verification ``` Agent has passed Goodstack review, and is awaiting email verification. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 Webhook received successfully Invalid webhook payload or signature verification failed --- # Agent verification was rejected ``` POST /webhooks/agent-verification-rejected ``` Triggered when an agent verification has been rejected by Goodstack. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 Webhook received successfully Invalid webhook payload or signature verification failed --- # Country compliance check result was stored ``` POST /webhooks/country-compliance-check-updated ``` Triggered when the result of the country compliance check is stored for an organisation in one of your programs, and again whenever it is re-evaluated to a different outcome because the organisation's country-specific data changed. Only sent for programs whose configuration requires country-specific regulations. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 Webhook received successfully Invalid webhook payload or signature verification failed --- # Donation cancelled ``` POST /webhooks/donation-cancelled ``` The donation process has been terminated, either by the donor or due to system issues. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 Webhook received successfully Invalid webhook payload or signature verification failed --- # Donation record created ``` POST /webhooks/donation-created ``` A new donation record has been successfully created in the system. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 Webhook received successfully Invalid webhook payload or signature verification failed --- # Donation disbursed ``` POST /webhooks/donation-disbursed ``` The donated funds have been transferred to the intended nonprofit. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 Webhook received successfully Invalid webhook payload or signature verification failed --- # Payment received for hosted donation ``` POST /webhooks/donation-hosted-payment-received ``` Payment has been received from the user for a hosted donation. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 Webhook received successfully Invalid webhook payload or signature verification failed --- # Donation payment successful ``` POST /webhooks/donation-payment-successful ``` The payment for the donation has been successfully completed and verified. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 Webhook received successfully Invalid webhook payload or signature verification failed --- # Donation reassigned ``` POST /webhooks/donation-reassigned ``` The 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. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 Webhook received successfully Invalid webhook payload or signature verification failed --- # Donation settled ``` POST /webhooks/donation-settled ``` The donated funds have been received and reconciled by the partner foundation. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 Webhook received successfully Invalid webhook payload or signature verification failed --- # Eligibility subscription was updated ``` POST /webhooks/eligibility-subscription-updated ``` Triggered when an eligibility subscription has been updated. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 Webhook received successfully Invalid webhook payload or signature verification failed --- # Monetary donation verification progressed ``` POST /webhooks/monetary-donation-verification-progressed ``` Triggered when a monetary donation verification has progressed, such as a check succeeding, failing, the verification being approved, rejected, expired, or reopened. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 Webhook received successfully Invalid webhook payload or signature verification failed --- # Monetary donation verification received ``` POST /webhooks/monetary-donation-verification-received ``` Triggered when a new monetary donation verification has been received and created. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 Webhook received successfully Invalid webhook payload or signature verification failed --- # Monitoring subscription was updated ``` POST /webhooks/monitoring-subscription-updated ``` Triggered when a monitoring subscription has been updated. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 Webhook received successfully Invalid webhook payload or signature verification failed --- # Webhook Subscriptions ## [📄️List Webhook Subscriptions](/docs/api/list-webhook-subscriptions.md) [List Webhook Subscriptions](/docs/api/list-webhook-subscriptions.md) ## [📄️Create a Webhook Subscription](/docs/api/create-a-webhook-subscription.md) [Create a Webhook Subscription. Specify the event types to subscribe to and the URL where the notifications will be sent, the URL must be https.](/docs/api/create-a-webhook-subscription.md) ## [📄️Retrieve a Webhook Subscription](/docs/api/retrieve-a-webhook-subscription.md) [Retrieve a Webhook Subscription](/docs/api/retrieve-a-webhook-subscription.md) ## [📄️Edit a Webhook Subscription](/docs/api/edit-a-webhook-subscription.md) [Edit a Webhook Subscription](/docs/api/edit-a-webhook-subscription.md) ## [📄️Delete a Webhook Subscription](/docs/api/delete-a-webhook-subscription.md) [Delete a Webhook Subscription](/docs/api/delete-a-webhook-subscription.md) --- # Validation request was approved ``` POST /webhooks/validation-request-approved ``` Triggered when a validation request has been approved by Goodstack. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 Webhook received successfully Invalid webhook payload or signature verification failed --- # Validation request was rejected ``` POST /webhooks/validation-request-rejected ``` Triggered when a validation request has been rejected by Goodstack. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 Webhook received successfully Invalid webhook payload or signature verification failed --- # Validation submission was created ``` POST /webhooks/validation-submission-created ``` Triggered when a validation submission has been created. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 Webhook received successfully Invalid webhook payload or signature verification failed --- # Validation submission has failed ``` POST /webhooks/validation-submission-failed ``` Triggered when a validation submission has failed validation checks. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 Webhook received successfully Invalid webhook payload or signature verification failed --- # Validation submission has passed all checks ``` POST /webhooks/validation-submission-succeeded ``` Triggered when a validation submission has successfully passed all validation checks. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 Webhook received successfully Invalid webhook payload or signature verification failed --- # Validation submission status has updated ``` POST /webhooks/validation-submission-updated ``` Triggered when a validation submission status has been updated. ## Request[​](#request "Direct link to request") ## Responses[​](#responses "Direct link to Responses") * 200 * 400 Webhook received successfully Invalid webhook payload or signature verification failed --- # Concepts Refer to the pages in this section for standard values, conventions and options for configuration and testing used across our product stack. ## Get started[​](#get-started "Direct link to Get started") ##### [Platform formats](/docs/concepts/Platform/formats.md) [Read more](/docs/concepts/Platform/formats.md) ##### [Webhooks overview](/docs/concepts/Platform/webhooks.md) [Read more](/docs/concepts/Platform/webhooks.md) ##### [Verifications overview](/docs/concepts/Verifications/Checks/validations-overview.md) [Read more](/docs/concepts/Verifications/Checks/validations-overview.md) ##### [Outcome emails](/docs/concepts/Verifications/outcome-emails.md) [Read more](/docs/concepts/Verifications/outcome-emails.md) --- # Currency reference ## Account currency[​](#account-currency "Direct link to Account currency") Your Goodstack account will use a single default currency for reporting purposes. Default currencies can be one of the following: | Name | Currency Code | | ------------------ | ------------- | | US Dollar | `USD` | | Euro | `EUR` | | British Pound | `GBP` | | Australian Dollar | `AUD` | | New Zealand Dollar | `NZD` | ## Donation currencies[​](#donation-currencies "Direct link to Donation currencies") For donations via our hosted gateway, we support payments in over 135+ currencies. Refer to [Stripe's documentation](https://stripe.com/docs/currencies?locale=en-GB) for a full list and corresponding currency codes. --- # Localization The following locales are supported in our donation API: | Name | Locale Code | Displayed as | | ------------------- | ------------------------- | ------------ | | English | `en-US`, `en-GB`, `en-IE` | English | | Danish | `da-DK` | Dansk | | German (Swiss) | `de-CH` | Deutsch CH | | German (Germany) | `de-DE` | Deutsch DE | | Dutch | `nl-NL` | Dutch | | Spanish (Spain) | `es-ES` | Español ES | | Spanish (Mexico) | `es-MX` | Español MX | | French (Canada) | `fr-CA` | Français CA | | French (France) | `fr-FR` | Français FR | | Italian | `it-IT` | Italiano | | Polish | `pl-PL` | Polski | | Portuguese (Brazil) | `pt-BR` | Português BR | | Swedish | `sv-SE` | Svenska | | Japanese | `ja-JP` | 日本語 | | Korean | `ko-KR` | 한국어 | --- # Test cards The following card details can be used to test various different card transaction outcomes in the Hosted Donation Gateway sandbox environment. | Outcome | Number | Expiry | CVV | | ------------------ | ---------------------- | ------ | --- | | Approval | 💳 4242 4242 4242 4242 | 12/34 | 567 | | Standard Decline | 💳 4000 0000 0000 0002 | 12/34 | 567 | | Insufficient Funds | 💳 4000 0000 0000 9995 | 12/34 | 567 | | 3D secure | 💳 4000 0027 6000 3184 | 12/34 | 567 | --- # Test organizations The following organisations are available for making donations in the sandbox. You can use these to test your integration. | Nonprofit | Country | Organisation Id (sandbox) | | ------------------------- | -------------- | ---------------------------------------------------- | | Cardiac Risk in the Young | United Kingdom | `sandbox_organisation_000000CAtB7DzFq1GCfc7OgkBRA5Q` | | Movember | United Kingdom | `sandbox_organisation_000000CAtPlM0ZJH4bItiaxy8QeQo` | | CoppaFeel | United Kingdom | `sandbox_organisation_000000CAtQ3Hg6MPhlpgLp0wFzDAZ` | | One Tree Planted | USA | `sandbox_organisation_000000CAlkqp362q1XITlspdfAVYn` | | Black Girls Code | USA | `sandbox_organisation_000000CCHPwCBY6yPXbEZFzbjbSaJ` | | Australian Red Cross | Australia | `sandbox_organisation_000000C8uLzNbF0EjY14q8kVixlqg` | | Food Ladder | Australia | `sandbox_organisation_000000CAt6wLOZ1ie2dlnMjzRhayB` | | Le Refuge | France | `sandbox_organisation_000000C70DCCGPP1KeD460X6kg9Yf` | --- # Countries The following countries are supported by our APIs and can also be retrieved via the [List Countries](/docs/api/list-available-countries.md) endpoint. Note that we use ISO 3166 alpha-3 format for country codes. | Name | Code | | ---------------------------------------------------------- | ----- | | Afghanistan | `AFG` | | Åland Islands | `ALA` | | Albania | `ALB` | | Algeria | `DZA` | | American Samoa | `ASM` | | Andorra | `AND` | | Angola | `AGO` | | Anguilla | `AIA` | | Antarctica | `ATA` | | Antigua and Barbuda | `ATG` | | Argentina | `ARG` | | Armenia | `ARM` | | Aruba | `ABW` | | Australia | `AUS` | | Austria | `AUT` | | Azerbaijan | `AZE` | | Bahamas (the) | `BHS` | | Bahrain | `BHR` | | Bangladesh | `BGD` | | Barbados | `BRB` | | Belarus | `BLR` | | Belgium | `BEL` | | Belize | `BLZ` | | Benin | `BEN` | | Bermuda | `BMU` | | Bhutan | `BTN` | | Bolivia (Plurinational State of) | `BOL` | | Bonaire, Sint Eustatius and Saba | `BES` | | Bosnia and Herzegovina | `BIH` | | Botswana | `BWA` | | Bouvet Island | `BVT` | | Brazil | `BRA` | | British Indian Ocean Territory (the) | `IOT` | | Brunei Darussalam | `BRN` | | Bulgaria | `BGR` | | Burkina Faso | `BFA` | | Burundi | `BDI` | | Cabo Verde | `CPV` | | Cambodia | `KHM` | | Cameroon | `CMR` | | Canada | `CAN` | | Cayman Islands (the) | `CYM` | | Central African Republic (the) | `CAF` | | Chad | `TCD` | | Chile | `CHL` | | China | `CHN` | | Christmas Island | `CXR` | | Cocos (Keeling) Islands (the) | `CCK` | | Colombia | `COL` | | Comoros (the) | `COM` | | Congo (the) | `COG` | | Congo (the Democratic Republic of the) | `COD` | | Cook Islands (the) | `COK` | | Costa Rica | `CRI` | | Côte d'Ivoire | `CIV` | | Croatia | `HRV` | | Curaçao | `CUW` | | Cyprus | `CYP` | | Czechia | `CZE` | | Denmark | `DNK` | | Djibouti | `DJI` | | Dominica | `DMA` | | Dominican Republic (the) | `DOM` | | Ecuador | `ECU` | | Egypt | `EGY` | | El Salvador | `SLV` | | Equatorial Guinea | `GNQ` | | Eritrea | `ERI` | | Estonia | `EST` | | Ethiopia | `ETH` | | Falkland Islands (the) \[Malvinas] | `FLK` | | Faroe Islands (the) | `FRO` | | Fiji | `FJI` | | Finland | `FIN` | | France | `FRA` | | French Guiana | `GUF` | | French Polynesia | `PYF` | | French Southern Territories (the) | `ATF` | | Gabon | `GAB` | | Gambia (the) | `GMB` | | Georgia | `GEO` | | Germany | `DEU` | | Ghana | `GHA` | | Gibraltar | `GIB` | | Greece | `GRC` | | Greenland | `GRL` | | Grenada | `GRD` | | Guadeloupe | `GLP` | | Guam | `GUM` | | Guatemala | `GTM` | | Guernsey | `GGY` | | Guinea | `GIN` | | Guinea-Bissau | `GNB` | | Guyana | `GUY` | | Haiti | `HTI` | | Heard Island and McDonald Islands | `HMD` | | Holy See (the) | `VAT` | | Honduras | `HND` | | Hong Kong | `HKG` | | Hungary | `HUN` | | Iceland | `ISL` | | India | `IND` | | Indonesia | `IDN` | | Iraq | `IRQ` | | Ireland | `IRL` | | Isle of Man | `IMN` | | Israel | `ISR` | | Italy | `ITA` | | Jamaica | `JAM` | | Japan | `JPN` | | Jersey | `JEY` | | Jordan | `JOR` | | Kazakhstan | `KAZ` | | Kenya | `KEN` | | Kiribati | `KIR` | | Korea (the Republic of) | `KOR` | | Kosovo | `XKX` | | Kuwait | `KWT` | | Kyrgyzstan | `KGZ` | | Lao People's Democratic Republic (the) | `LAO` | | Latvia | `LVA` | | Lebanon | `LBN` | | Lesotho | `LSO` | | Liberia | `LBR` | | Libya | `LBY` | | Liechtenstein | `LIE` | | Lithuania | `LTU` | | Luxembourg | `LUX` | | Macao | `MAC` | | Macedonia (the former Yugoslav Republic of) | `MKD` | | Madagascar | `MDG` | | Malawi | `MWI` | | Malaysia | `MYS` | | Maldives | `MDV` | | Mali | `MLI` | | Malta | `MLT` | | Marshall Islands (the) | `MHL` | | Martinique | `MTQ` | | Mauritania | `MRT` | | Mauritius | `MUS` | | Mayotte | `MYT` | | Mexico | `MEX` | | Micronesia (Federated States of) | `FSM` | | Moldova (the Republic of) | `MDA` | | Monaco | `MCO` | | Mongolia | `MNG` | | Montenegro | `MNE` | | Montserrat | `MSR` | | Morocco | `MAR` | | Mozambique | `MOZ` | | Myanmar | `MMR` | | Namibia | `NAM` | | Nauru | `NRU` | | Nepal | `NPL` | | Netherlands (the) | `NLD` | | New Caledonia | `NCL` | | New Zealand | `NZL` | | Nicaragua | `NIC` | | Nigeria | `NGA` | | Niger (the) | `NER` | | Niue | `NIU` | | Norfolk Island | `NFK` | | Northern Mariana Islands (the) | `MNP` | | Norway | `NOR` | | Oman | `OMN` | | Pakistan | `PAK` | | Palau | `PLW` | | Palestine, State of | `PSE` | | Panama | `PAN` | | Papua New Guinea | `PNG` | | Paraguay | `PRY` | | Peru | `PER` | | Philippines (the) | `PHL` | | Pitcairn | `PCN` | | Poland | `POL` | | Portugal | `PRT` | | Puerto Rico | `PRI` | | Qatar | `QAT` | | Réunion | `REU` | | Romania | `ROU` | | Russian Federation (the) | `RUS` | | Rwanda | `RWA` | | Saint Barthélemy | `BLM` | | Saint Helena, Ascension and Tristan da Cunha | `SHN` | | Saint Kitts and Nevis | `KNA` | | Saint Lucia | `LCA` | | Saint Martin (French part) | `MAF` | | Saint Pierre and Miquelon | `SPM` | | Saint Vincent and the Grenadines | `VCT` | | Samoa | `WSM` | | San Marino | `SMR` | | Sao Tome and Principe | `STP` | | Saudi Arabia | `SAU` | | Senegal | `SEN` | | Serbia | `SRB` | | Seychelles | `SYC` | | Sierra Leone | `SLE` | | Singapore | `SGP` | | Sint Maarten (Dutch part) | `SXM` | | Slovakia | `SVK` | | Slovenia | `SVN` | | Solomon Islands | `SLB` | | Somalia | `SOM` | | South Africa | `ZAF` | | South Georgia and the South Sandwich Islands | `SGS` | | South Sudan | `SSD` | | Spain | `ESP` | | Sri Lanka | `LKA` | | Suriname | `SUR` | | Svalbard and Jan Mayen | `SJM` | | Swaziland | `SWZ` | | Sweden | `SWE` | | Switzerland | `CHE` | | Taiwan (Province of China) | `TWN` | | Tajikistan | `TJK` | | Tanzania, United Republic of | `TZA` | | Thailand | `THA` | | Timor-Leste | `TLS` | | Togo | `TGO` | | Tokelau | `TKL` | | Tonga | `TON` | | Trinidad and Tobago | `TTO` | | Tunisia | `TUN` | | Turkey | `TUR` | | Turkmenistan | `TKM` | | Turks and Caicos Islands (the) | `TCA` | | Tuvalu | `TUV` | | Uganda | `UGA` | | Ukraine | `UKR` | | United Arab Emirates (the) | `ARE` | | United Kingdom of Great Britain and Northern Ireland (the) | `GBR` | | United States Minor Outlying Islands (the) | `UMI` | | United States of America (the) | `USA` | | Uruguay | `URY` | | Uzbekistan | `UZB` | | Vanuatu | `VUT` | | Venezuela (Bolivarian Republic of) | `VEN` | | Viet Nam | `VNM` | | Virgin Islands (British) | `VGB` | | Virgin Islands (U.S.) | `VIR` | | Wallis and Futuna | `WLF` | | Western Sahara\* | `ESH` | | Yemen | `YEM` | | Zambia | `ZMB` | | Zimbabwe | `ZWE` | --- # Formats ## Dates[​](#dates "Direct link to Dates") Dates are encoded as strings following the ISO 8601 standard `2021-08-01T12:00:00.000Z`. ## Pagination[​](#pagination "Direct link to 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, `pageSize` is set to 25. ## Metadata[​](#metadata "Direct link to Metadata") Some objects have a `metadata` parameter that you can use to store key-value data. You may specify up to 20 keys, with key names up to 40 characters long and values up to 250 characters long. ## IDs[​](#ids "Direct link to IDs") All ID fields are case-sensitive. For example, abc123 and ABC123 are treated as different IDs. ## Monetary Values[​](#monetary-values "Direct link to Monetary Values") Throughout the API, monetary values are encoded as integers representing the amount in the smallest currency unit, for example 100 cents for a donation of $1.00. Currency is encoded as a string following the ISO 4217 standard (e.g. USD). ## Errors[​](#errors "Direct link to 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. | Code | Title | Description | | ---- | --------------------- | ------------------------------------------- | | 200 | Success | Request successful | | 204 | No Content | Request successful with no content returned | | 400 | Bad Request | Request was invalid | | 401 | Unauthorised | Authorization header is invalid | | 403 | Forbidden | Insufficient permissions | | 404 | Not Found | Resource does not exist | | 429 | Too Many Requests | Rate Limited | | 500s | Internal Server Error | Something went wrong with the Goodstack API | ## Enum Values[​](#enum-values "Direct link to Enum Values") Generally enum fields in our API are open-ended. New values may be added to an enum and this is not considered a breaking change. If your code handles enum values exhaustively — for example, a switch without a default case, or deserialisation into a strict typed enum — an unrecognised value may cause errors. We recommend adding a fallback handler for unknown enum values, so that any new value that does reach your integration degrades gracefully rather than failing. Some endpoints that return enum fields support filtering by those values as query parameters, letting you restrict responses to the values your integration supports. --- # 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](/docs/api/list-webhook-subscriptions.md). ## Attributes[​](#attributes "Direct link to Attributes") At the top level Webhook payloads will contain object and data fields. The data record contains the following fields: | Parameter | Type | Description | | ----------- | ------ | -------------------------------------------------------- | | `id` | string | Id of the event | | `createdAt` | string | Timestamp of when the event was created | | `eventType` | string | The type of the event e.g. `validation_request.approved` | | `eventData` | object | Data associated with the event | ## Event types[​](#event-types "Direct link to Event types") | Event | Description | | ---------------------------------------------- | -------------------------------------------------------------------- | | `validation_request.approved` | Validation request was approved | | `validation_request.rejected` | Validation request was rejected | | `agent_verification.pending_review` | Agent has passed email verification and is awaiting Goodstack review | | `agent_verification.pending_user_verification` | Agent has passed Goodstack review and is awaiting email verification | | `agent_verification.approved` | Agent verification was approved | | `agent_verification.rejected` | Agent verification was rejected | | `monitoring_subscription.updated` | Monitoring subscription was updated | | `eligibility_subscription.updated` | Eligibility subscription was updated | | `validation_submission.created` | Validation submission was created | | `validation_submission.succeeded` | Validation submission has passed all checks | | `validation_submission.failed` | Validation submission has failed | | `validation_submission.updated` | Validation submission status has updated | | `donation.hosted.payment_received` | Hosted donation payment was received | | `donation.created` | Donation was created | | `donation.settled` | Donation was settled | | `donation.disbursed` | Donation was disbursed | | `donation.payment_successful` | Donation payment was successful | | `donation.cancelled` | Donation was cancelled | | `donation.reassigned` | Donation was reassigned | ## Webhook source IP addresses[​](#webhook-source-ip-addresses "Direct link to Webhook source IP addresses") The full list of IP addresses that webhook notifications may come from. Production environment: * `54.228.234.204` * `54.76.67.168` * `34.248.188.89` * `34.243.152.218` Sandbox environment: * `79.125.45.124` * `54.76.168.240` * `99.81.243.145` * `54.220.118.167` ## Webhook responses[​](#webhook-responses "Direct link to 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[​](#verifying-webhooks "Direct link to 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. If you are subscribed to webhook notifications for updates on the status of verification or donation events, you can verify that webhooks sent to your infrastructure are genuine by verifying the webhook signature. info Get in contact for support with webhook verification on any systems not listed below * JavaScript * PHP * Python * Go * Java ``` const crypto = require('crypto') const express = require('express') const app = express() const endpointSecret = 'sk_xxxxxxxxxxxxxxxxxxxxxxxx' const verifySignature = (secret, payload, signature) => { const hmac = crypto.createHmac('sha256', secret) hmac.write(payload) hmac.end() return hmac.read().toString('hex') === signature } app.post( '/webhook', express.raw({ type: 'application/json' }), (request, response) => { const payload = request.body const sig = request.headers['goodstack-signature'] if (!verifySignature(endpointSecret, payload, sig)) { return response.status(401).send() } // perform business logic based on event response.status(202).send() }, ) ``` ``` ``` ``` from flask import Flask, request, abort import hmac import hashlib app = Flask(__name__) API_SECRET_KEY = 'my_webhook_api_secret' def verify_webhook(data, hmac_header): # Calculate HMAC (hex encoded) digest = hmac.new(API_SECRET_KEY.encode('utf-8'), data, digestmod=hashlib.sha256).hexdigest() return hmac.compare_digest(digest, hmac_header) @app.route('/webhook', methods=['POST']) def handle_webhook(): # Get raw body data = request.get_data() # Compare HMACs verified = verify_webhook(data, request.headers.get('Goodstack-Signature')) if not verified: abort(401) # Do something with the webhook return ('', 200) ``` ``` package verifywebhook import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "io" "net/http" ) // Hook is an inbound webhook type Hook struct { Signature string Payload []byte } func signBody(secret, body []byte) string { // Calculate HMAC-SHA256 (hex encoded) computed := hmac.New(sha256.New, secret) computed.Write(body) return hex.EncodeToString(computed.Sum(nil)) } func (h *Hook) SignedBy(secret []byte) bool { expected := signBody(secret, h.Payload) return hmac.Equal([]byte(expected), []byte(h.Signature)) } func (h *Hook) Extract(dst interface{}) error { return json.Unmarshal(h.Payload, dst) } func New(req *http.Request) (hook *Hook, err error) { hook = new(Hook) if req.Method != "POST" { return nil, errors.New("Unknown method!") } // Extract signature if hook.Signature = req.Header.Get("Goodstack-Signature"); len(hook.Signature) == 0 { return nil, errors.New("No signature!") } // Get raw body hook.Payload, err = io.ReadAll(req.Body) return } func Parse(secret []byte, req *http.Request) (hook *Hook, err error) { hook, err = New(req) // Compare HMACs if err == nil && !hook.SignedBy(secret) { err = errors.New("Invalid signature") } return } ``` ``` import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import java.io.*; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; class SignatureVerificationHandler implements HttpHandler { private String encodingAlgorithm = "HmacSHA256"; private String secretKey = "someSecretKeyThatShouldBeSecure"; private String headerThatContainsSignature = "Goodstack-Signature"; private boolean verifySignature(String payload, String signature) throws NoSuchAlgorithmException, InvalidKeyException { var sha256_HMAC = Mac.getInstance(encodingAlgorithm); SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(), encodingAlgorithm); sha256_HMAC.init(secretKeySpec); byte[] hash = sha256_HMAC.doFinal(payload.getBytes()); StringBuilder hexString = new StringBuilder(); for (byte b : hash) { hexString.append(String.format("%02x", b)); } return hexString.toString().equals(signature); } @Override public void handle(HttpExchange httpExchange) throws IOException { Headers headers = httpExchange.getRequestHeaders(); String signature = headers.getFirst(headerThatContainsSignature); String payload = readBody(httpExchange); try { boolean isValidMessage = verifySignature(payload, signature); if (isValidMessage){ returnWithStatus(httpExchange, 200); return; } } catch (Exception e) { returnWithStatus(httpExchange, 500); return; } returnWithStatus(httpExchange, 401); } private String readBody(HttpExchange httpExchange) throws IOException { BufferedInputStream stream = new BufferedInputStream(httpExchange.getRequestBody()); ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); for (int result = stream.read(); result != -1; result = stream.read()) { byteBuffer.write((byte) result); } return byteBuffer.toString(StandardCharsets.UTF_8); } private void returnWithStatus(HttpExchange httpExchange, int httpStatusCode) throws IOException { httpExchange.sendResponseHeaders(httpStatusCode, 0); httpExchange.getResponseBody().close(); } } ``` --- # Additional fields info This feature only applies to the [hosted setup]() Additional fields are added on top of the hosted forms' base fields to give you the ability to collect more information about your users. As an example, you may want to understand which type of plan the applicant will require. This can be helpful when trying to understand if they should be redirected to a sales person or be re-directed to a self-serve sign up flow. ### How can you add them?[​](#how-can-you-add-them "Direct link to How can you add them?") info Get in touch with our product & engineering team to add additional fields to your flow. They’ll help you set up the extra fields you want. #### Number of fields[​](#number-of-fields "Direct link to Number of fields") → You can add up to 10 additional fields to your flow. #### Type of fields[​](#type-of-fields "Direct link to Type of fields") You can add the following type of fields to your flow: * Short text - up to 256 characters * Number * Multiple choice checkboxes - up to 5 options allowed * Dropdown select - up to 15 options allowed * Website You can mark any field as required. #### Other notes[​](#other-notes "Direct link to Other notes") * Adding additional fields will add one extra screen to your flow * The extra screen will be added right before the Terms & Conditions screen #### Code example[​](#code-example "Direct link to Code example") ``` { "partnerFields": { "account_id": "12345", "required_seats": "10", "tier": "enterprise" } } ``` #### Example[​](#example "Direct link to Example") Below is an example of how the additional fields look like in the hosted form - this is what it will look like for your customers (with your custom branding of course). ![Additional Fields](/assets/images/Additional_fields-9564a568cad5667564e7f105248c21a6.png) --- # Applications inbox info Applications inbox is where you can see all the applications that came in for your product The applications inbox was created to enable you to keep track of the applications for your program and you can access it through the Applications tab under the Compliance section of the dashboard. ### Applications table[​](#applications-table "Direct link to Applications table") info Get in touch with our product & engineering team if you don’t have access to the dashboard yet In the applications table you can see the main information about each application: * Nonprofit: name of the nonprofit the applicant belong to * Agent name: name of the nonprofit’s applicant * Agent email: email of the nonprofit’s applicant * Validation: status of the application in 3 different states - Pending, Succeeded and Failed * Application date: date when the applicant/nonprofit applied for the program ![Applications](/assets/images/Applications-839f7569e2713456a8fc19fdf9f49c1d.png) ### Application details[​](#application-details "Direct link to Application details") In the application details you’ll be able to see a checks summary where you can understand the status of each check and for example where an application has failed if that’s the case. You will also see a details section with all the information about the nonprofit, as well as a detailed list of the compliance checks. If you’re using [Additional fields](/docs/concepts/Verifications/additional-fields.md), you’ll also see them in this view. ![Application details](/assets/images/Application_detailed_view-ad8883925dce02d61c7d50a2ac9c3633.png) --- # Agent Verification ## Agent Verification[​](#agent-verification "Direct link to Agent Verification") ### What is agent verification?[​](#what-is-agent-verification "Direct link to What is agent verification?") info This check prevents abuse of your nonprofit program. Agent verification allows you to see whether a user is associated with the nonprofit they’ve claimed to work for, and has the right to receive a product donation on that nonprofit’s behalf. There are two parts to agent verification: #### 1. Nonprofit association[​](#1-nonprofit-association "Direct link to 1. Nonprofit association") This is the core part of agent verification. With this check, Goodstack determines whether the user is associated with the nonprofit they’ve applied on behalf of. In some cases, we can detect this automatically by comparing the users email domain against the nonprofit’s website domain(s). In cases where the user’s email domain doesn’t match the nonprofit website domain(s), we contact the nonprofit via email and use their response to determine whether the user is associated with their nonprofit. #### 2. User verification (optional)[​](#2-user-verification-optional "Direct link to 2. User verification (optional)") Goodstack performs this check to make sure the user has access to the email address they’ve applied with. Checking this email access is key to preventing abuse of your nonprofit program. We strongly recommend Goodstack performs this check on your behalf if: * You don’t perform an email verification check on users; * A user can own an account for your product that’s different from the email address that Goodstack has for the user; * Both of the above. #### Statuses[​](#statuses "Direct link to Statuses") The status field in an Agent Verification request refers to stage of the verification process that the user is in. Below are the statuses and the meaning behind them: * `pending`: Goodstack is yet to run their verification checks and the user is yet to verify their email address. This status can never been reached if user verification is turned off. * `pending_review`: Goodstack has yet to run their own verification checks on the applicant. If user verification is turned on for partner, then this status will be mean the user has already verified their email address. * `pending_user_verification`: If user verification is turned on for the partner, this status means the user has been associated with the nonprofit but is yet to verify their email address. This status can never been reached if user verification is turned off. * `approved`: Goodstack has deemed that the applicant is genuine and associated with the nonprofit * `rejected`: Goodstack has deemed that the applicant is not genuine and has not been associated with the nonprofit. --- # Compliance Checks ## Compliance Checks[​](#compliance-checks "Direct link to Compliance Checks") #### What is a Monitoring Subscription?[​](#what-is-a-monitoring-subscription "Direct link to What is a Monitoring Subscription?") A Monitoring Subscription allows you to see if a nonprofit has passed or failed Goodstack's compliance checks while also being notified of any future changes. If a compliance check has not been completed on creation, the Goodstack team will look to complete the compliance checks according to your agreed SLA. ### Statuses[​](#statuses "Direct link to Statuses") The `status` field in the Monitoring Subscription entity refers to the state of the subscription. Below are the statuses and the meaning behind them: * `pending`: Goodstack is yet to run their compliance checks for the nonprofit. * `pending_validation_request`: The Monitoring Subscription was created with a Validation Request and that Validation Request is yet to be handled by Goodstack. * `validation_request_failed`: The Monitoring Subscription was created with a Validation Request that has since been rejected. * `live`: Goodstack have completed their initial compliance checks for the nonprofit and the subscription will be listening for any changes in the future so partners can be notified. * `cancelled`: The monitoring subscription has been cancelled. Cancelled meaning that the subscription will no longer be active and any new updates for the organization in question will be ignored. ### Results[​](#results "Direct link to Results") The `results` field in the Monitoring Subscription object provides the result of each compliance check that Goodstack completes. It will be present on creation and as checks are completed by Goodstack, it will become populated with results and when complete will look like the following: ``` { "results": { "complianceStatus": "fail" | "pass", "registration": { "active": "no" | "yes" }, "warning": { "status": "flag" | "clear" }, "sanction": { "status": "flag" | "clear" }, "adverseMedia": { "status": "clear" }, "hateSpeech": { "status": "flag" } } } ``` ### Compliance checks[​](#compliance-checks-1 "Direct link to Compliance checks") Below you can find a small summary of what each check and result means. #### Compliance status[​](#compliance-status "Direct link to Compliance status") Compliance status refers to whether Goodstack will cooperate and support the given organization. This status is not designed to be a binding decision as to how a partner can work with a nonprofit directly, and only indicates whether the nonprofit could be supported via the Goodstack platform. * Pass: This means Goodstack would be happy to support the nonprofit and disburse funds to it. * Fail: Goodstack has deemed the nonprofit high risk, due to specific marked compliance warnings, and will not cooperate with this organization. #### Adverse media[​](#adverse-media "Direct link to Adverse media") The adverse media flag refers to Goodstack performing an interrogation of third-party data sources for negative news associated with a nonprofit and to expose information that indicates the organization would breach Goodstack Validation policies. * Clear: Goodstack hasn’t found any negative news associated with the given nonprofit. * Flag: Goodstack has found some news associated with the given nonprofit which is a cause for concern and has flagged them. #### Hate speech[​](#hate-speech "Direct link to Hate speech") The hate speech flag refers to Goodstack checks in finding any derogatory or dehumanising statements, that incite hate, violence or direct attack against people or groups on the basis of the following characteristics: age, race, ethnicity, nationality, immigration status, religion, disability, sexual orientation, sex, gender identity, victims of a major violent event and their kin and veteran status. * Clear: Goodstack hasn’t found any indication that this organization has shared any hate speech before. * Flag: Goodstack has found evidence of the organization using hate speech and has flagged it. #### Sanction & warning[​](#sanction--warning "Direct link to Sanction & warning") These flags are apart of our AML checks. We look across warning and sanction lists to see if the given organization has any previous history with money laundering. * Clear: Goodstack has not found any record of money laundering warnings/ sanctions. * Flag: Goodstack has found a record of the organization in a sanction or warning list and has flagged it. #### Registration[​](#registration "Direct link to Registration") This result refers to whether the given organization is active legally. The results for this section are either `Yes` or `No`. --- # Eligibility Review ## Eligibility Review[​](#eligibility-review "Direct link to Eligibility Review") ### What is an Eligibility Subscription?[​](#what-is-an-eligibility-subscription "Direct link to What is an Eligibility Subscription?") An Eligibility Subscription allows a Partner to see if a nonprofit fits into their acceptance criteria while also being notified if that changes at any point in the future. ### Statuses[​](#statuses "Direct link to Statuses") The `status` field in the Eligibility Subscription entity refers to the state of the subscription. Below are the statuses and the meaning behind them: * `pending`: Goodstack is yet to provide a classification tag for the nonprofit. * `pending_validation_request`: The Eligibility Subscription was created with a validation request and that validation request is yet to be handled by Goodstack. * `validation_request_failed`: The Eligibility Subscription was created with a validation request that has since been rejected. * `cannot_define_eligibility`: Goodstack has not been able to provide a classification for the nonprofit and therefore will not update the `eligibilityStatus`. We recommend implementing logic to either fail the organization or to escalate them for internal review. * `live`: Goodstack have provided a classification tag for the nonprofit and the subscription will be listening for any changes in the future so you can be notified. * `cancelled`: The Partner has cancelled the Eligibility Subscription. ### Results[​](#results "Direct link to Results") The results field in the Eligibility Subscription provides the answer to whether the organization in question matches a partners eligibility criteria. We do this by matching the classification tags that have been given by our ops team with the tags in the Partners acceptance criteria. The results object will become populated once the Goodstack team has classified the organization. ``` results: { "eligibilityStatus": "fail", } ``` OR ``` results: { "eligibilityStatus": "pass", } ``` --- # Validations Overview ## Validations Overview[​](#validations-overview "Direct link to Validations Overview") We offer rigorous compliance checks to ensure that the organisations in our database – nonprofits, social impact organisations, and educational institutions – are valid, compliant and meet our customers’ eligibility criteria. We complete validation checks every time: 1. A nonprofit is claimed in our causes portal 2. A donation request comes in for a nonprofit that hasn’t been validated by us 3. An applicant is submitted through an [Education verification]() flow info The same four checks below run for both nonprofit and education verification. The Eligibility check adapts to the configuration’s allowed organisation types — for education it determines the institution type (K–12, university, college, etc.) rather than nonprofit activity tags. info Below you can see a brief description what each check means. It’s worth noting that all checks are asynchronous and typically take between 1 minute and 72h before they are complete ### Validation[​](#validation "Direct link to Validation") We make sure that an organisation actually exists. For this, we check official registry websites from the nonprofit or institution’s country and search for it (using either name or registration code). Organisations that have already been validated by us will have this check done automatically. ### Compliance[​](#compliance "Direct link to Compliance") info Learn more about compliance in our compliance checks page Due diligence checks ensuring a nonprofit is not involved in any damaging activities. The checks we do are the following: * Sanction checks * Warning checks * Adverse media checks * Hate speech checks ### Eligibility[​](#eligibility "Direct link to Eligibility") info Learn more about eligibility in our eligibility review page For nonprofits and social impact organisations, we tag the organisation based on what their purpose is (e.g. climate change). For educational institutions, we tag the organisation based on its type (e.g. K–12, university, college, community college). The result matches against your eligibility settings to either approve or reject the applicant. Get in touch with our team so they can set up your configuration for you. ### Agent verification[​](#agent-verification "Direct link to Agent verification") info Learn more about agent verification in our agent verification page We confirm the applicant is associated with the organisation (the nonprofit, or the school for education verification). To do that we do a few things: * Domain matching: the email domain and the organisation’s website domain are the same * If the domain doesn’t match, we contact the organisation to confirm the applicant is associated with it * Email verification: the applicant must verify they have access to the email --- # Discount redemption info This feature only applies to the [hosted setup]() Discount redemption is a feature that enables successful applicants to get a code or link they can use to redeem access to your product. When successful—and if you’ve subscribed to this feature—any successful applicant will get the code or link in their [outcome email](/docs/concepts/Verifications/outcome-emails.md). ## How can you add them?[​](#how-can-you-add-them "Direct link to How can you add them?") info Get in touch with our product & engineering team to set up discount redemption for you. ### What’s needed[​](#whats-needed "Direct link to What’s needed") We’ll need to work out with you whether you’ll provide discounts to the applicants with a code or a dynamic link. For example, if it’s a code you provide, you’ll need to give us a list of codes we can provide when they’re successful - for this, we usually accept a list of pre-defined codes (e.g. CSV file) Other notes If codes or links doesn’t work for you, we’ll be happy to learn a bit more about how you provide discounts to your customers. ## Example[​](#example "Direct link to Example") Below is an example of an acceptance email with code redemption functionality—this is what it will look like for your customers with your logo next to Goodstack’s. ![Email with discount code](/assets/images/Discount_redemption-fad7a1e5b339626db03b631634f9ae09.png) --- # Outcome Emails ## Outcome Emails[​](#outcome-emails "Direct link to Outcome Emails") info This feature only applies to the hosted setup Outcome emails are automated emails sent by the Goodstack platform to users that have applied for a product discount—letting them know whether their application has been accepted or rejected. Outcome emails are sent after all the checks you subscribed to are completed on the relevant application. ### How can you add them?[​](#how-can-you-add-them "Direct link to How can you add them?") info Get in touch with our product & engineering team to customize the email copy you want for when users are approved and rejected. #### What’s needed[​](#whats-needed "Direct link to What’s needed") To update the emails according to your copy guidelines, we’ll need: * Copy for an acceptance email * Copy for a rejected email #### Example[​](#example "Direct link to Example") Below is an example of an acceptance email—this is what it will look like for your customers with your logo next to Goodstack’s. ![markdown file changed](/assets/images/Outcome_emails-example-8041190eb9dd747d9ec3a8e11aeb2ac0.png) --- # Solution guides Choose the guide that matches the workflow you want to build. Hosted guides are the fastest route to launch; API guides give you more control over the user experience. ##### [Hosted nonprofit verification]() [Use Goodstack-hosted screens to verify organisations with minimal front-end work.]() [Read more]() ##### [Nonprofit verification API]() [Build your own verification flow for nonprofit eligibility checks.]() [Read more]() ##### [Education verification API]() [Verify students and teachers for education offers using your own application UI.]() [Read more]() ##### [Hosted donations]() [Launch donations with Goodstack-hosted checkout and confirmation pages.]() [Read more]() ##### [Donations API]() [Build a custom donation experience and send payments through Goodstack.]() [Read more]() --- # Nonprofit verification Use Goodstack to confirm that an organisation is a nonprofit or other approved cause, check the applicant's association with it, and apply your program's eligibility and compliance rules. Nonprofit verification is commonly used for nonprofit discounts, product credits, and access to impact-program benefits. ## Choose an integration[​](#choose-an-integration "Direct link to Choose an integration") | Integration | Best for | | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | [API implementation]() | A fully embedded application experience where your product owns the interface and client actions. | | [Hosted implementation]() | A Goodstack-hosted application flow with less front-end work. | Both approaches use a program configuration set up during onboarding. That configuration determines the accepted organisation types, verification checks, applicant fields, document requirements, and outcome behavior. Confirm those choices before you build client logic around them. ## What the API journey covers[​](#what-the-api-journey-covers "Direct link to What the API journey covers") The native API guide walks through: 1. searching for an existing organisation; 2. handling an approved manual-entry path when the organisation is not found; 3. creating a validation submission from your backend; 4. collecting configured supporting documents; 5. translating submission states into applicant actions; 6. receiving webhooks and reconciling the latest result. Start with the [Nonprofit verification API guide](), or compare it with the [hosted flow](). For student or teacher programs, use the [Education verification guides](). ## How the checks fit together[​](#how-the-checks-fit-together "Direct link to How the checks fit together") Your configuration can combine organisation validation, applicant/agent verification, eligibility, and compliance checks. Do not assume every configuration runs the same checks; confirm the checks and resulting client actions for your assigned configuration during onboarding. --- # Nonprofit verification: API Use the Goodstack API to build a nonprofit verification flow inside your product. Your interface collects the application, your backend submits it to Goodstack, webhook events prompt updates, and reconciliation reads confirm the current outcome. This guide covers one server-side integration from organisation search through entitlement. If you want Goodstack to host the applicant interface, use the [hosted verification flow](). ## Before you start[​](#before-you-start "Direct link to Before you start") Before you build the full flow, confirm that your publishable key can call the sandbox [List Available Countries API](/docs/api/list-available-countries.md). You will also need: * a publishable key for organisation-discovery requests; * a secret key for submissions, documents, reads, and webhook subscriptions; * the `configurationId` that Goodstack assigned to your program. Retrieve your keys from the [Keys page in the Partner dashboard](https://partners.goodstack.io/keys). If the page or sandbox credentials are not available to your account, ask your Goodstack implementation contact to enable access. Pass either API key as the raw value of the `Authorization` header. Do not add `Bearer`. Keep your secret key on your backend and never expose it in browser code, mobile apps, logs, or source control. Confirm your program configuration Before you build the applicant flow, confirm these details with your Goodstack implementation contact: * which organisation types and countries your program accepts; * whether applicants can enter an organisation that is not in search results; * which applicant fields and supporting documents your checks require; * whether an outcome can change after the first decision; * the failure and reapplication experience you want applicants to see. These choices affect request validation and client behavior. The configuration endpoints return basic configuration data, but do not expose every check or document requirement. Use `https://sandbox-api.goodstack.io` while you build. The examples below use `` and `` placeholders for your sandbox credentials. ### API map[​](#api-map "Direct link to API map") These are the Goodstack requests used by the core flow and its manual-entry branch: | Purpose | Request | Credential | Runs from | | ---------------------------------------- | ----------------------------------------------- | -------------------------------------------------------- | ----------------------- | | Populate the country selector | `GET /v1/countries` | Publishable key in a browser; secret key on your backend | Browser or your backend | | Find a registered nonprofit | `GET /v1/organisations` | Publishable key in a browser; secret key on your backend | Browser or your backend | | List registries for manual entry | `GET /v1/registries` | Publishable key in a browser; secret key on your backend | Browser or your backend | | Create a verification | `POST /v1/validation-submissions` | Secret key | Your backend | | Upload one supporting file when expected | `POST /v1/validation-submission-documents` | Secret key | Your backend | | Register result delivery | `POST /v1/webhook-subscriptions` | Secret key | Your backend | | Reconcile the current outcome | `GET /v1/validation-submissions/{submissionId}` | Secret key | Your backend | The three discovery requests can run directly from the browser because they accept a publishable key. You can instead proxy them through your backend—for example, to centralise caching, filtering, or rate limiting. In that design, the browser calls your backend and only your backend calls Goodstack. A backend proxy can use either key, but must never return or forward a secret key to the browser. Backend proxy rate limits Publishable-key requests are rate limited per source IP, while secret-key requests are rate limited per partner. When using a backend proxy, publishable-key traffic shares the proxy's outbound-IP allowance. Confirm current limits with Goodstack when planning higher-volume integrations. ## How the flow works[​](#how-the-flow-works "Direct link to How the flow works") ## 1. Find the organisation[​](#1-find-the-organisation "Direct link to 1. Find the organisation") Ask first for the country where the organisation is registered. Goodstack uses three-letter ISO country codes such as `GBR`, `FRA`, and `USA`. You can populate the country selector with the [List Available Countries API](/docs/api/list-available-countries.md). Search by country and organisation name with the [Search Organisations API](/docs/api/search-organisations.md). This read can run from your front end with a publishable key: ``` GET https://sandbox-api.goodstack.io/v1/organisations?countryCode=GBR&type[]=nonprofit&query=community%20foundation Authorization: Accept: application/json ``` You can also search with the exact `registryId` parameter. Present enough context for the applicant to distinguish similarly named organisations. A result is shaped like this: ``` { "data": [ { "id": "organisation_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "name": "Example Community Foundation", "displayName": "Example Community Foundation", "description": "A community foundation supporting local charities and projects.", "countryCode": "GBR", "types": ["nonprofit"], "logo": "https://assets.example.org/example-community-foundation.png", "registry": "registry_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "registryId": "1234567", "registryDetails": { "id": "registry_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "name": "Charity Commission", "englishName": "Charity Commission", "code": "CHC" }, "website": "https://foundation.example.org", "address": "10 Example Street, London, SW1A 1AA", "addressLine1": "10 Example Street", "addressLine2": null, "city": "London", "state": "England", "postcode": "SW1A 1AA" } ], "totalResults": 1, "pageNumber": 1, "pageSize": 25, "exhaustiveTotalResults": true, "object": "organisation" } ``` Some organisation fields can be `null` or absent. Use the registry number, location, and website to help applicants distinguish similarly named results; use the selected organisation's `id` as the stable API identifier. When the applicant selects a result, store its `id`. Send that value as `organisationId` when you create the submission. ### When the organisation is not found[​](#when-the-organisation-is-not-found "Direct link to When the organisation is not found") Most nonprofit programs allow applicants to enter an organisation that is not in the search results. When your program configuration allows it, offer a manual entry path and replace the search results with a form that collects the organisation's details. Goodstack uses this information to perform a Validation Request as part of the validation submission and assess the organisation's identity and nonprofit status. If the path is not enabled for your program, keep the applicant in your approved search or support flow instead of sending a manual-entry payload. Give the applicant a way to return to search, and explain that continuing starts an organisation review that normally requires official supporting evidence. An illustrative front-end structure could look like this; adapt the wording to your approved applicant experience: Illustrative applicant screen We couldn't find your organisation Try another name or registry number, or submit the organisation for review. ← Back to search Country United Kingdom⌄ Organisation name Enter the public name Official registry Select a registry⌄ Registry or charity number Enter the official identifier Official website https\:// i **What happens next** In most cases, you will provide official evidence of the organisation's nonprofit status for Goodstack to review. Applicant details come nextContinue → Carry the country selected for search into this form, while allowing the applicant to correct it. Use the [List Registries API](/docs/api/list-registries.md) to populate the registry selector for that country: ``` GET https://sandbox-api.goodstack.io/v1/registries?countryCode=GBR Authorization: Accept: application/json ``` Submit the selected registry's `name` as `registryName`, not its Goodstack `id`. Submit the organisation's own record number in that registry as `registryId`. If the correct registry is not listed, `registryName` also accepts the registry's official name as free text. For example: | Organisation jurisdiction | `registryName` example | `registryId` example | | ------------------------- | -------------------------- | --------------------------------- | | England and Wales | `Charity Commission` | Charity number, such as `1234567` | | United States | `Internal Revenue Service` | EIN, such as `12-3456789` | Use the exact `name` returned by the Registries API when one is available; the examples show how the registry name and the organisation's identifier map to separate submission fields. For the nonprofit-only configuration covered by this guide, collect these organisation details: | Field | Front-end guidance | | ------------------ | -------------------------------------------------------------------------------- | | `organisationName` | Ask for the organisation's public name. | | `registryName` | Offer the official registries returned for the selected country, plus free text. | | `registryId` | Label this for the selected registry, such as “charity number” or “EIN.” | | `website` | Ask for the organisation's official website. | | `countryCode` | Carry forward the selected three-letter ISO country code. | These requirements are scoped to the nonprofit-only manual path. When the applicant continues, collect their details and preferred language, then send everything to your backend using the [manual-entry submission payload](#2-create-the-validation-submission). Store the returned validation-submission ID before uploading the expected organisation evidence in [step 4](#4-upload-supporting-documents). Keep the benefit gated while Goodstack reviews the organisation and the other configured checks. Programs that accept social-impact organisations This guide's examples target a nonprofit-only configuration. If your program also accepts `social_impact` organisations, confirm the search filters and manual-entry requirements for that configuration before reusing these examples. ## 2. Create the validation submission[​](#2-create-the-validation-submission "Direct link to 2. Create the validation submission") Your backend creates a submission with its secret key. Always pass the `configurationId` assigned to the program; do not rely on an account default. You can list the IDs available to your account with the [Retrieve Partner Configurations API](/docs/api/retrieve-partners-configurations.md). The examples below assume that applicant/agent verification is enabled, so they include `firstName`, `lastName`, and `email`. Goodstack will confirm the fields required by your actual configuration. * Found in search * Manual entry Use this payload when the applicant selected a search result: ``` POST https://sandbox-api.goodstack.io/v1/validation-submissions Authorization: Idempotency-Key: cause_attempt__submission Content-Type: application/json { "configurationId": "hostedconfiguration_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "organisationId": "organisation_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "firstName": "Alex", "lastName": "Doe", "email": "alex.doe@example.org", "language": "en-GB", "metadata": { "applicationId": "application_12345" } } ``` Use this payload when the applicant entered the organisation's details manually: ``` POST https://sandbox-api.goodstack.io/v1/validation-submissions Authorization: Idempotency-Key: cause_attempt__submission Content-Type: application/json { "configurationId": "hostedconfiguration_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "organisationName": "Example Community Foundation", "registryName": "Charity Commission", "registryId": "1234567", "website": "https://foundation.example.org", "countryCode": "GBR", "firstName": "Alex", "lastName": "Doe", "email": "alex.doe@example.org", "language": "en-GB", "metadata": { "applicationId": "application_12345" } } ``` `metadata` lets you attach up to 20 of your own string values to the submission. Keys can contain up to 40 characters and values up to 250 characters. Include a stable application or account ID so webhook events can be reconciled with your system without using applicant details as the lookup key. Make retries safe Create one verification-attempt record in your system before calling Goodstack. Use one stable `Idempotency-Key` for that logical create request. If the request times out, retry with the same key and the identical payload. Use a different key for every other logical write, including each document upload. Never reuse a key with a changed payload or across two endpoints. Do not build around a fixed key-retention window. ### Store the response[​](#store-the-response "Direct link to Store the response") Goodstack returns `200 OK` with the current submission. Persist `data.id` before updating the applicant experience. The other fields and nested check objects depend on the configured checks: ``` { "data": { "id": "validationsubmission_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "status": "pending", "organisationId": "organisation_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "validationRequestId": null, "agentVerificationId": "agentverification_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "eligibilitySubscriptionId": "eligibilitysubscription_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "monitoringSubscriptionId": "monitoringsubscription_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "createdAt": "2026-08-07T14:30:00.000Z", "metadata": { "applicationId": "application_12345" } }, "object": "validation_submission" } ``` Store: * `data.id` as the canonical validation-submission ID; * `data.organisationId` when present; * each non-null component ID that your configuration returns; * your own application ID and the idempotency key used for the create request. A manual submission starts with `organisationId: null` and has a `validationRequestId`. If Goodstack validates the organisation, later reads and webhooks include its `organisationId`. Component IDs can be null when their checks are not configured, so your integration must not require every ID in the example. ## 3. Show the applicant the current state[​](#3-show-the-applicant-the-current-state "Direct link to 3. Show the applicant the current state") Drive the main applicant experience from the top-level submission `status`: | Status | Client action | | ----------- | --------------------------------------------------------------------------------------------------------------------- | | `pending` | Keep the benefit gated. Show an in-review state or the configured next step. | | `succeeded` | If your approved program policy maps success to access, apply the benefit. Store any newly assigned `organisationId`. | | `failed` | Keep the benefit gated and show your approved failure or recovery experience. | Do not assume the create response will be `pending`; handle all three statuses. Checks run asynchronously and can take from a few seconds to as long as 72 hours. When the response is `pending`, release the request and use result delivery and reconciliation rather than keeping the applicant's browser request open. Nested check statuses can explain progress, but they do not replace the top-level outcome. In particular, do not infer that the applicant needs association evidence from `agentVerification.status`. Follow the manual-entry evidence guidance in step 4, and use the requirements Goodstack confirmed for any additional document steps. Dynamic outcomes Some configurations can recalculate an outcome after the initial decision. If Goodstack confirms that dynamic outcomes are enabled for your program, process `validation_submission.updated` and apply your agreed entitlement or review policy to the latest retrieved status. ## 4. Upload supporting documents[​](#4-upload-supporting-documents "Direct link to 4. Upload supporting documents") Plan to collect organisation evidence when an applicant uses manual entry. Almost all manual-entry configurations require it because the organisation was not selected from Goodstack search results. Only omit this step when Goodstack explicitly confirms that your configuration is one of the rare exceptions. For an organisation selected from search results, request a document only when your configuration requires one. Goodstack may also require separate evidence of the applicant's association with the organisation. ### Recommend evidence for a manually entered nonprofit[​](#recommend-evidence-for-a-manually-entered-nonprofit "Direct link to Recommend evidence for a manually entered nonprofit") For the typical manual-entry path, ask the applicant for the clearest current official evidence of the organisation's nonprofit or charitable status. Recommend one or more of these documents, starting with the strongest evidence available in the organisation's jurisdiction: * a registration, recognition, or good-standing certificate issued by a charity, nonprofit, association, foundation, or other competent regulator; * a current official registry extract, status confirmation, registration decision, or renewal notice; * an official-gazette notice or government registration receipt that establishes the organisation's legal existence; * a tax-authority determination or recognition letter that explicitly confirms charitable, nonprofit, or tax-exempt status; * a certificate of incorporation, formation, or registration that identifies the entity's nonprofit legal form. If no single official document establishes both legal identity and nonprofit status, the applicant can also provide a constitution, articles of association, bylaws, trust deed, foundation charter, or similar governing document alongside the strongest official evidence available. Prefer documents that clearly show the legal name, registration or charity number, issuing authority, jurisdiction, status, and any issue, renewal, or expiry date. These details should match the manual-entry payload. Do not use a donation receipt, bank statement, website screenshot, or marketing material as the primary proof of nonprofit status. Upload nonprofit-status evidence with `documentType=validation_request`. Use `documentType=agent_verification` only when the document instead proves the applicant's association with the organisation. Your backend sends the file to the [Create Validation Submission Document API](/docs/api/create-validation-submission-document.md) with its secret key: ``` curl --request POST \ --url 'https://sandbox-api.goodstack.io/v1/validation-submission-documents' \ --header 'Authorization: ' \ --header 'Idempotency-Key: cause_attempt__document_1' \ --form 'validationSubmissionId=validationsubmission_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \ --form 'documentType=validation_request' \ --form 'file=@./registration-proof.pdf' ``` Let your HTTP client set the multipart `Content-Type`, including its boundary. Upload one file per request. Choose `documentType` by what the evidence supports: | `documentType` | Use it for | | -------------------- | ------------------------------------------------------------------- | | `validation_request` | Evidence about the organisation, such as registration documentation | | `agent_verification` | Evidence about the applicant's association with the organisation | Send the value explicitly rather than relying on the default. A successful upload returns: ``` { "data": { "id": "validationsubmissiondocument_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "validationSubmissionId": "validationsubmission_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "createdAt": "2026-08-07T14:35:00.000Z", "url": "https://assets.example.org/document.pdf", "type": "validation_request" }, "object": "validation_submission_document" } ``` The endpoint accepts JPEG, PNG, and PDF content up to 5 MB. Goodstack detects the file type from the file's bytes, not its extension or `Content-Type` label. A HEIC image renamed to `.jpg`, for example, is still rejected. Validate or convert files before upload, and do not log document contents or presigned document URLs. After upload, store `data.id` with the verification attempt, keep the benefit gated, and show the applicant the in-review state. The upload response is not an outcome; wait for a webhook or the next scheduled reconciliation read. ## 5. Configure result delivery[​](#5-configure-result-delivery "Direct link to 5. Configure result delivery") Complete result delivery before accepting applications. Keeping this setup next to event processing makes the full asynchronous result path easier to implement and test. Use the [Create Webhook Subscription API](/docs/api/create-a-webhook-subscription.md) from your backend with your secret key. The request uses the plural `events` property and requires an HTTPS URL: ``` POST https://sandbox-api.goodstack.io/v1/webhook-subscriptions Authorization: Content-Type: application/json { "events": [ "validation_submission.created", "validation_submission.succeeded", "validation_submission.failed", "validation_submission.updated" ], "url": "https://example.org/webhooks/goodstack" } ``` The create response contains the secret used to verify this subscription's deliveries: ``` { "data": { "id": "webhooksubscription_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "events": [ "validation_submission.created", "validation_submission.succeeded", "validation_submission.failed", "validation_submission.updated" ], "url": "https://example.org/webhooks/goodstack", "secret": "", "createdAt": "2026-08-07T14:00:00.000Z", "updatedAt": null, "deletedAt": null }, "object": "webhook_subscription" } ``` Store `data.id` and `data.secret` in your backend's secret store as soon as you receive them. The subscription secret is separate from both API keys; do not expose it to applicants, commit it, or write it to logs. Subscribe to all four events so the same handler works if dynamic outcomes are enabled later. ## 6. Process and reconcile results[​](#6-process-and-reconcile-results "Direct link to 6. Process and reconcile results") The endpoint configured in step 5 receives these Validation Submission events: * `validation_submission.created`; * `validation_submission.succeeded`; * `validation_submission.failed`; * `validation_submission.updated`. The `updated` event is used by configurations whose outcomes can change. An event uses this envelope: ``` { "object": "event", "data": { "id": "event_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "createdAt": "2026-08-07T15:00:00.000Z", "eventType": "validation_submission.succeeded", "eventData": { "id": "validationsubmission_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "status": "succeeded", "organisationId": "organisation_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "agentVerificationId": "agentverification_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "eligibilitySubscriptionId": "eligibilitysubscription_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "monitoringSubscriptionId": "monitoringsubscription_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "validationSubmissionHostedConfigurationId": "hostedconfiguration_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "metadata": { "applicationId": "application_12345" } } } } ``` Treat delivery as an at-least-once signal: 1. Verify `Goodstack-Signature` against the raw request body with the webhook subscription's secret, not an API key. Compute the hex-encoded HMAC-SHA256 over the unmodified body and compare the received and expected signatures in constant time. 2. Deduplicate delivery on the event `data.id`. 3. Persist the event and enqueue any slow work. 4. Return a `2xx` response after the event is safely stored. 5. Retrieve the current submission before applying a state transition that must be authoritative. Make the state or entitlement update idempotent by submission ID and the latest retrieved state, not only by event ID. Distinct events can describe the same effective outcome, and retries or out-of-order deliveries must not grant a benefit twice or overwrite a newer state. ### Retrieve the latest submission[​](#retrieve-the-latest-submission "Direct link to Retrieve the latest submission") Use webhooks as the prompt for state changes. Use the [Retrieve Validation Submission API](/docs/api/retrieve-a-validation-submission.md) as the authoritative read for reconciliation, support tooling, or recovery after an uncertain delivery: ``` GET https://sandbox-api.goodstack.io/v1/validation-submissions/validationsubmission_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx Authorization: Accept: application/json ``` The response contains the latest submission and configured check results: ``` { "data": { "id": "validationsubmission_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "status": "succeeded", "organisationId": "organisation_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "agentVerification": { "id": "agentverification_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "status": "approved", "rejectionReasonCode": null }, "eligibility": { "status": "live", "results": { "eligibilityStatus": "pass", "confirmedActivitySubTags": [], "rejectedActivitySubTags": [] } }, "metadata": { "applicationId": "application_12345" } }, "object": "validation_submission" } ``` Nested check objects and result keys are configuration-dependent. Treat the read's top-level `status`—not a partial webhook snapshot or a nested check status—as the current verification outcome. Do not assume that every non-terminal transition produces a webhook. Run a bounded reconciliation job that periodically retrieves submissions for which your system has not recorded a terminal outcome, and stops or escalates them according to the cadence and age limits agreed during onboarding. If dynamic outcomes are enabled, also follow the agreed resynchronization policy after a terminal outcome. Apply the same idempotent state-transition logic to webhook-triggered and scheduled reads. ## 7. Deliver the benefit and communicate the outcome[​](#7-deliver-the-benefit-and-communicate-the-outcome "Direct link to 7. Deliver the benefit and communicate the outcome") After retrieving the latest submission, use its top-level status to complete the applicant journey. Keep the verification outcome separate from your own benefit-fulfilment state: Goodstack determines whether the verification succeeded, while your system determines whether and when the benefit has been delivered. ### Complete the happy path[​](#complete-the-happy-path "Direct link to Complete the happy path") When `status` is `succeeded` and your program policy maps success to the benefit: 1. Persist the successful outcome and the latest `organisationId` against your application. 2. Create or update one fulfilment record for the verification attempt. 3. Grant the benefit, account access, discount, entitlement, or other program outcome exactly once. 4. Record whether fulfilment is pending, complete, or needs operational attention. 5. Communicate the result and the next step to the applicant. Key fulfilment on your application or validation-submission ID so processing the same webhook again cannot grant the benefit twice. Acknowledge the webhook after storing the event; benefit fulfilment can run asynchronously through your normal job or queue infrastructure. Tell the applicant what they need to know about both verification and fulfilment: | Fulfilment state | Applicant communication | | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | Complete | Confirm verification, name the benefit now available, and explain how to access or use it. | | In progress | Confirm verification, explain that benefit delivery is underway, and give the next update or expected timing if known. | | Needs operational help | Keep the verification marked successful, explain that benefit delivery is delayed, and provide the appropriate support path. | Use your own applicant-facing application reference where one is helpful. Do not include API keys, webhook details, raw check or screening results, uploaded-document links, or internal failure data in the confirmation. If the benefit has limits, an expiry, or another activation step, include that information so the applicant knows what to do next. If dynamic outcomes are enabled, agree how a later status change affects an already delivered benefit and how you will communicate that change. Do not silently remove access without the review and communication policy agreed for the program. ### When the submission status is `failed`[​](#when-the-submission-status-is-failed "Direct link to when-the-submission-status-is-failed") A `validation_submission.failed` event is a verification outcome, not an API error. Persist the outcome against the verification attempt, keep the benefit gated, and show the failure or recovery experience agreed for your program. A failed event can include a `failureReasons` array with structured context about the checks that produced the outcome. For a typical manual-entry failure, it can look like this: ``` { "object": "event", "data": { "id": "event_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "createdAt": "2026-08-07T16:00:00.000Z", "eventType": "validation_submission.failed", "eventData": { "id": "validationsubmission_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "status": "failed", "organisationId": null, "validationRequestId": "validationrequest_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "metadata": { "applicationId": "application_12345" }, "failureReasons": [ { "check": "validation_request", "reason": { "rejectionReasonCode": "incorrect_documentation" } } ] } } } ``` Use each `check` value to choose an internal handling path: | `check` | What it tells you | | -------------------- | -------------------------------------------------------------------------------------------------------------- | | `validation_request` | Goodstack could not validate the manually entered organisation. | | `agent_verification` | Goodstack could not verify the applicant's association with the organisation. | | `eligibility` | The organisation did not meet the configured eligibility criteria, or its eligibility could not be determined. | | `compliance` | The organisation did not pass the configured compliance policy. | | Other or absent | Keep the benefit gated and route the attempt through your generic support or review path. | The shape of `reason` depends on the check. Validation Request and agent-verification failures can include a `rejectionReasonCode`; eligibility and compliance failures contain their relevant result data. Treat `check`, `reason`, and any nested values as extensible. Use them as machine-readable context for support, analytics, and recovery routing, not as applicant-facing copy. In particular, do not expose raw compliance screening results. A Validation Request failure may be the only item because downstream checks cannot proceed until the organisation is validated. The array therefore explains the outcome; it is not necessarily a list of every check that would have failed. ### Decide the recovery path[​](#decide-the-recovery-path "Direct link to Decide the recovery path") Agree during onboarding which outcomes allow the applicant to correct information, replace a document, contact support, or start another attempt. Do not automatically create a new submission whenever a failed webhook arrives. If your program permits reapplication, treat it as a new logical verification attempt with a new create-submission idempotency key, and retain the earlier submission ID for audit and support. If dynamic outcomes are enabled, a failed submission can change later; apply the agreed dynamic-outcome policy before starting a competing attempt. ## Troubleshoot API request failures[​](#troubleshoot-api-request-failures "Direct link to Troubleshoot API request failures") An unsuccessful API request is not a verification outcome. A request can fail before Goodstack accepts or completes it; a submission reaches `failed` only after Goodstack has created the verification and evaluated its configured checks. Do not show an applicant a verification-failure message solely because an API request failed. | Situation | Handling | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | `400` validation error | Inspect the error response and correct the request. A changed write is a new logical request and needs a new idempotency key. | | `401` or `403` | Treat this as an integration configuration problem. Check the key, environment, and account scope before retrying. | | `404` | Check the endpoint, resource ID, configuration ID, and whether sandbox and production values were mixed. | | `429` or a `5xx` response | Retry with backoff and honor `Retry-After` when present. For a write, keep the request body and idempotency key unchanged. | | Timeout or lost response | The result is uncertain. Retry the identical request with the same idempotency key, then reconcile before creating another verification attempt. | Keep the applicant's benefit gated while the request is unresolved. Record enough context to diagnose the request, but do not log API keys, uploaded documents, or other sensitive payload data. ## Go live[​](#go-live "Direct link to Go live") Before changing to `https://api.goodstack.io`: * switch the base URL and API keys together so sandbox and production credentials cannot mix; * confirm the production `configurationId` and document requirements; * verify and deduplicate webhooks in a staging-like environment; * test each public status and every configured document path; * test benefit fulfilment retries without granting the benefit twice; * approve applicant messages for success, delayed fulfilment, failure, and reapplication; * confirm how your product grants, withholds, or revisits the benefit; * ensure logs and analytics contain IDs, not secret keys or document contents. ## Next steps[​](#next-steps "Direct link to Next steps") * Compare this flow with [hosted nonprofit verification](). * Review the [Create Validation Submission](/docs/api/create-a-validation-submission.md), [Retrieve Validation Submission](/docs/api/retrieve-a-validation-submission.md), and [document upload](/docs/api/create-validation-submission-document.md) references. * Apply the webhook verification requirements in step 6 before accepting live events. --- # Nonprofit verification – hosted form implementation The hosted setup for your nonprofit discount program enables you to have it set up in no time and with barely any engineering resource. You will be able to quickly have your branded discount program live and start onboarding nonprofits to your product. Below you can see a summary of what you can customize: ![Customization options](/assets/images/Application_form_summary_-_1-e02f5a183a1b243a628c01c0c87913a9.png) And the process for hosted applications: ![Validation request process](/assets/images/Application_form_summary_-_2-a24c627eb9e0d0902d1ad89533e2e09b.png) ## 1: Customize the hosted application form[​](#1-customize-the-hosted-application-form "Direct link to 1: Customize the hosted application form") You can customize the look and feel of the hosted form. To do this you need to provide us: * A logo or icon that will be displayed on the form. For best results, we recommend that your logo is a square image larger than 128 x 128 pixels. JPG, JPEG and PNG file types are supported. * A CSS hex colour value representing the primary branding colour for the hosted form e.g. `#EB3434` ## 2: Implement the hosted application form[​](#2-implement-the-hosted-application-form "Direct link to 2: Implement the hosted application form") info It takes \~2h to set up the nonprofit validation hosted setup Add an endpoint on your server that [creates a validation invite](/docs/api/create-a-validation-invite.md). A validation invite is a one off invitation to complete a validation submission. To do this you will need to make a POST request to Goodstack’s API. ### Create a validation invite[​](#create-a-validation-invite "Direct link to Create a validation invite") ### Configure a validation invite[​](#configure-a-validation-invite "Direct link to Configure a validation invite") Validation Invites have the following parameters that you can use for one time validations. * `redirectUrl` → URL that the hosted form redirects to after the flow is completed. * `metadata` → an object that you can use to attach key-value data to the Validation Invite. ### Example response[​](#example-response "Direct link to Example response") You will then be returned a response matching the format of the example below: ``` { "data": { "id": "validationinvite_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "status": "open", "metadata": { "key1": "value1", "key2": "value2" }, "language": "en-GB", "redirectUrl": "https://example.com/finish", "url": "https://validation.goodstack.org/validate/validationinvite_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx?token=asasjhd", "createdAt": "2020-10-13T17:46:54.000Z", "expiresAt": "2020-10-20T17:46:54.000Z" }, "object": "validation_invite" } ``` After creating the Validation Invite, direct your user to the URL for the hosted form returned in the response. ## 3: Receive and process results[​](#3-receive-and-process-results "Direct link to 3: Receive and process results") info To receive results, you'll need the Webhook Subscriptions API. When users submit your hosted form, you'll to be able to process the results for each check. For that, you can subscribe to notifications by creating webhook subscriptions using the [Create a Webhook Subscription](/docs/api/create-a-webhook-subscription.md) endpoint. For the hosted form, you will need to subscribe to at least the events: * `validation_submission.created` * `validation_submission.succeeded` * `validation_submission.failed` Before subscribing you will need to create server-side event handlers. You can verify that the incoming webhook notification is sent by Goodstack by following the [instructions here](/docs/concepts/Platform/webhooks.md#verifying-webhooks). This will update you when you receive new applications and when a final outcome is reached. You may also wish to subscribe to updates for the individual verification checks if you want to track more granular status updates in your system. info You are encouraged to process webhooks as quickly as possible and return a successful response as soon as you have verified the webhook and stored it for processing. You could use a queue to store the incoming events and process asynchronously. If the webhook handler responds too slowly, this could lead to a timeout and a retry of the webhook event. --- # Education Verification ## Education Verification – Overview[​](#education-verification--overview "Direct link to Education Verification – Overview") Verify that applicants to your education program belong to a recognised educational institution. The same Goodstack API powers both **student verification** (typically higher education) and **teacher verification** (typically K–12, with some higher education). If you have an existing nonprofit discount program, the Education verification flow will feel familiar – it uses the same Validation Submission API as our [Nonprofit verification]() flow, configured for educational institutions. You can have full control over the applicant sign-up flow with the [API setup](). It supports two integration patterns: a fully **server-side** integration, and a **browser-direct** pattern where applicant documents are uploaded straight from the browser to Goodstack and never touch your infrastructure. ### Setups[​](#setups "Direct link to Setups") * [API setup](): Connect directly to our APIs to build a fully customizable flow for your education program – server-side or browser-direct ### Education verification checks[​](#education-verification-checks "Direct link to Education verification checks") info You can learn more about how Goodstack runs verification checks in the [validation overview page](/docs/concepts/Verifications/Checks/validations-overview.md). Each Education verification submission runs three checks: a **Validation Request** to confirm the institution is legitimate, an **Eligibility Check** to determine the institution type (university, college, K–12, etc.), and an **Agent Verification** to confirm the applicant is actually associated with the institution. --- # Education verification: API ## Overview[​](#overview "Direct link to Overview") The Education Verification API confirms two things in one workflow: that an institution is a legitimate school, and that your applicant is a student or teacher there. Use it to gate education discounts and offers. ### Who you can verify[​](#who-you-can-verify "Direct link to Who you can verify") * **Students** – focuses on higher education (universities, colleges, and community colleges). * **Teachers** – primarily covers K–12 (primary and secondary schools), with select higher education coverage. ### How it works[​](#how-it-works "Direct link to How it works") This guide walks you through a direct API integration, step by step. The code examples cover both segments – use the Student / Teacher tabs in the code blocks to see the payload for your use case. End to end, an Education verification looks like this from the applicant's perspective: info Prefer a low-code option? If you don't want to build the front-end UI yourself, Goodstack can host the verification flow for you. Contact your account manager to get started. ## 1. Choose your integration path[​](#1-choose-your-integration-path "Direct link to 1. Choose your integration path") You can integrate the Education Verification API in two ways. Both run the same verification checks – the difference is which of your systems talks to Goodstack, and whether applicant documents pass through your infrastructure. | | **Server-side** | **Direct browser** | | ------------------- | ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | Who calls Goodstack | Your backend, with your secret key | Your backend creates a validation invite; your front end then calls Goodstack directly with your publishable key | | Document handling | Your backend receives files and forwards them to Goodstack | Your front end sends files directly to Goodstack – they never touch your servers | | Document timing | Upload after the submission exists, when your configuration calls for documentation | Upload up-front, before the submission is created | | Configuration | A `configurationId`, passed with each submission | A `hostedConfigId`, passed when creating each validation invite | | Best for | The simplest integration, when handling documents on your servers is acceptable | Programs that must avoid receiving, storing, or logging applicant documents (privacy / compliance requirements) | After the shared setup, choose the path that matches how you want to handle applicant documents: * [Server-side integration](#5-server-side-integration) – your backend sends the submission and any supporting documents to Goodstack using your secret key. * [Direct browser integration](#6-direct-browser-integration) – your front end sends documents and the submission directly to Goodstack, after your backend creates a validation invite. ## 2. Get your API keys[​](#2-get-your-api-keys "Direct link to 2. Get your API keys") info Contact your account manager to get access to the dashboard. Before you start, you need access to the dashboard to retrieve your Goodstack API keys. Go to the [`API Keys` tab in your Partner dashboard settings](https://partners.goodstack.io/settings) to access your `Publishable Key` and your `Secret Key`. Authenticate your API requests by passing a key in the `Authorization` request header. Use the secret key for server-to-server requests, and the publishable key for requests your front end makes to public API endpoints. Pass the key as the **raw** header value, like this: ``` Authorization: ``` Never publish your secret key in source control or use it in front-end code. ## 3. Find the applicant's institution[​](#3-find-the-applicants-institution "Direct link to 3. Find the applicant's institution") ### 3a. Collect the country[​](#3a-collect-the-country "Direct link to 3a. Collect the country") Collect the applicant's country as the first step of your flow. Passing a `countryCode` when you search for institutions makes results much more accurate, so we strongly recommend it. You can retrieve the full list of supported countries (with their ISO three-letter codes) from the [Countries API](/docs/api/list-available-countries.md): ``` GET /v1/countries 200 OK { "data": [ { "code": "GBR", "name": "United Kingdom" } ], "object": "Country" } ``` Country codes are three-letter ISO codes (`USA`, `GBR`, `FRA`, etc.). ### 3b. Search for the institution[​](#3b-search-for-the-institution "Direct link to 3b. Search for the institution") Call the [Search Organisations endpoint](/docs/api/search-organisations.md) with the applicant's `countryCode`, `type[]=education`, and a search query. The `type[]=education` filter restricts results to schools, colleges, universities, and other recognised educational institutions in Goodstack's database. You can authenticate this endpoint with your publishable key. * Student * Teacher ``` GET /v1/organisations?type[]=education&countryCode=USA&query=greenfield 200 OK { "data": [ { "id": "organisation_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "name": "Greenfield University", "countryCode": "USA" } ] } ``` ``` GET /v1/organisations?type[]=education&countryCode=GBR&query=westbridge 200 OK { "data": [ { "id": "organisation_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "name": "Westbridge Primary School", "countryCode": "GBR" } ] } ``` ### 3c. Filter by education stage (optional)[​](#3c-filter-by-education-stage-optional "Direct link to 3c. Filter by education stage (optional)") If your program only applies to certain education levels – for example a teacher program limited to K–12 – you can narrow search results by **education stage**. Goodstack tags each institution in its database with the stages it teaches: | Tag | Stage | | ---------------------- | ----------------------------------------- | | `gs.stage:pre-primary` | Pre-primary (nursery, kindergarten) | | `gs.stage:primary` | Primary education | | `gs.stage:secondary` | Secondary education | | `gs.stage:higher` | Higher education (universities, colleges) | An institution can carry **more than one** stage tag – a school that spans kindergarten through primary has both `gs.stage:pre-primary` and `gs.stage:primary`. Pass the stages you want as a comma-separated list in the `tags[]` parameter. A comma-separated list matches institutions with **any** of the listed tags: * Student * Teacher ``` GET /v1/organisations?type[]=education&countryCode=USA&query=greenfield&tags[]=gs.stage:higher ``` Returns only higher-education institutions. ``` GET /v1/organisations?type[]=education&countryCode=GBR&query=westbridge&tags[]=gs.stage:primary,gs.stage:secondary ``` Returns only institutions that teach primary or secondary education (or both) – i.e. K–12. Prefer `tags[]` over `notTags[]` The endpoint also supports a `notTags[]` exclusion parameter, but for stage filtering an **include list is safer**. `notTags[]` excludes an institution if it carries *any* of the listed tags – so excluding `gs.stage:pre-primary` from a K–12 search would also drop a school that teaches both pre-primary *and* primary, even though it qualifies. With `tags[]`, that school is correctly included. Repeating the parameter (`tags[]=X&tags[]=Y`) requires **all** listed tags instead – see the [Search Organisations reference](/docs/api/search-organisations.md) for the full matching rules. ### 3d. Handle found and manual institutions[​](#3d-handle-found-and-manual-institutions "Direct link to 3d. Handle found and manual institutions") If the applicant selects their institution from the search results, store the returned `organisationId` for the Validation Submission request. If no matching institution appears, switch your form to manual entry. Collect the following fields from the applicant: * `organisationName` – the name of the school *(required)* * `countryCode` – three-letter ISO code *(required)* * `website` – the school's official website *(optional but highly recommended)* * `addressLine1`, `addressLine2`, `city`, `state`, `postal` – the school's address *(optional but recommended)* A website and address help our verification team locate the institution faster. The submission also requires a `language` field – you collect this with the applicant's details in [step 4](#4-collect-applicant-details). Registry fields not required for Education The general [Create Validation Submission](/docs/api/create-a-validation-submission.md) reference lists `registryName` and `registryId` as required fields. **Education verification does not require them**, and you should not ask the applicant for them – students and teachers rarely know their institution's registry identifier, and it often does not apply. Goodstack eliminates the registry requirement whenever your configuration's allowed organisation types include `education` (whether on its own or alongside `nonprofit` / `social_impact`). Omit these fields from your manual-entry payload and Goodstack accepts the submission. ## 4. Collect applicant details[​](#4-collect-applicant-details "Direct link to 4. Collect applicant details") Collect the applicant's name, email, and preferred language. You send these with the Validation Submission, and the Agent Verification check uses them to confirm the applicant is associated with the institution (for example, by checking that their email domain matches the school). * Student * Teacher ``` { "firstName": "Alex", "lastName": "Doe", "email": "alex.doe@greenfield.edu", "language": "en-US" } ``` ``` { "firstName": "Jane", "lastName": "Smith", "email": "jane.smith@westbridge.sch.uk", "language": "en-GB" } ``` The `language` field accepts any [RFC 5646](https://datatracker.ietf.org/doc/html/rfc5646) language code (`en-US`, `en-GB`, `fr-FR`, etc.). Each submission runs three checks automatically – you don't call separate endpoints to create them: * [Validation Check](/docs/concepts/Verifications/Checks/validations-overview.md#validation) – confirms the institution exists and is legitimate * [Eligibility Check](/docs/concepts/Verifications/Checks/eligibility-review.md) – determines the institution type (e.g. university, college, K–12) * [Agent Verification](/docs/concepts/Verifications/Checks/agent-verification.md) – confirms the applicant is associated with the institution info All verification checks run asynchronously. Many submissions resolve within seconds to a couple hours, however in some cases it may take up to 72 hours. ## 5. Server-side integration[​](#5-server-side-integration "Direct link to 5. Server-side integration") Choose this path when your backend can receive applicant documents and forward them to Goodstack. Your backend makes all write calls to Goodstack with your **secret key**. ### 5a. Create the validation submission[​](#5a-create-the-validation-submission "Direct link to 5a. Create the validation submission") Send the institution and applicant details to the [Create Validation Submission endpoint](/docs/api/create-a-validation-submission.md). Reference your validation configuration with `configurationId`. Our onboarding team sets up configurations, and your account manager provides your `configurationId` before you go live. You can list the configurations active on your account via the [Retrieve Partner Configurations API](/docs/api/retrieve-partners-configurations.md). #### With `organisationId`[​](#with-organisationid "Direct link to with-organisationid") Use this payload when the applicant selected their institution from search results: * Student * Teacher ``` POST https://api.goodstack.io/v1/validation-submissions Authorization: Idempotency-Key: ver_attempt_01HZR9YJ6K8X4R7Q2N5P3M1A0B_submission Content-Type: application/json { "configurationId": "configuration_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "organisationId": "organisation_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "firstName": "Alex", "lastName": "Doe", "email": "alex.doe@greenfield.edu", "language": "en-US" } ``` ``` POST https://api.goodstack.io/v1/validation-submissions Authorization: Idempotency-Key: ver_attempt_01HZR9Z4BN2QK7Y5J8C6D3W0EF_submission Content-Type: application/json { "configurationId": "configuration_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "organisationId": "organisation_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "firstName": "Jane", "lastName": "Smith", "email": "jane.smith@westbridge.sch.uk", "language": "en-GB" } ``` #### With manual institution details[​](#with-manual-institution-details "Direct link to With manual institution details") Use this payload when the applicant entered institution details manually. Omit `registryName` and `registryId` – see [Registry fields not required for Education](#3d-handle-found-and-manual-institutions). * Student * Teacher ``` POST https://api.goodstack.io/v1/validation-submissions Authorization: Idempotency-Key: ver_attempt_01HZR9YJ6K8X4R7Q2N5P3M1A0B_submission Content-Type: application/json { "configurationId": "configuration_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "organisationName": "Greenfield University", "website": "https://www.greenfield.edu", "addressLine1": "500 College Avenue", "city": "San Jose", "state": "California", "postal": "95112", "countryCode": "USA", "firstName": "Alex", "lastName": "Doe", "email": "alex.doe@greenfield.edu", "language": "en-US" } ``` ``` POST https://api.goodstack.io/v1/validation-submissions Authorization: Idempotency-Key: ver_attempt_01HZR9Z4BN2QK7Y5J8C6D3W0EF_submission Content-Type: application/json { "configurationId": "configuration_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "organisationName": "Westbridge Primary School", "website": "https://westbridge.sch.uk", "addressLine1": "12 High Street", "city": "Westbridge", "state": "England", "postal": "WB1 2CD", "countryCode": "GBR", "firstName": "Jane", "lastName": "Smith", "email": "jane.smith@westbridge.sch.uk", "language": "en-GB" } ``` Submissions created with manual institution details have a `null` `organisationId` on creation. If our verification team confirms the institution details, Goodstack creates the organisation record and exposes its ID via webhook and the [Retrieve Validation Submission API](/docs/api/retrieve-a-validation-submission.md). ### 5b. Store the submission response[​](#5b-store-the-submission-response "Direct link to 5b. Store the submission response") Goodstack responds with `200 OK` and the submission. Store the IDs you'll need later – at minimum the submission `id`, and `agentVerification.id` if you support document upload: ``` { "data": { "id": "validationsubmission_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "status": "pending", "createdAt": "2026-04-15T10:30:00.000Z", "organisationId": "organisation_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "organisationName": "Greenfield University", "agentVerificationId": "agentverification_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "agentVerification": { "id": "agentverification_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "firstName": "Alex", "lastName": "Doe", "email": "alex.doe@greenfield.edu", "status": "pending", "rejectionReasonCode": null }, "eligibilitySubscriptionId": "eligibilitysubscription_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "validationRequestId": "validationrequest_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "validationInviteId": null, "metadata": {}, "monitoring": { "status": null, "results": null }, "eligibility": { "status": "live", "results": null } }, "object": "ValidationSubmission" } ``` The submission also accepts an optional `metadata` object – an arbitrary key-value record that links the submission back to your own systems (account IDs, application form fields, etc.). Goodstack returns metadata with the submission on every read and in every webhook. Protect against duplicate submissions Create one verification attempt in your system before calling Goodstack, and use a stable `Idempotency-Key` for the create-submission request. If the request times out, retry with the same idempotency key and the same payload. After Goodstack responds, store the returned `data.id` against the attempt and reuse that submission if the applicant refreshes. You can also include your attempt ID in `metadata` so reads and webhooks are easier to reconcile. ### 5c. Show the applicant the current state[​](#5c-show-the-applicant-the-current-state "Direct link to 5c. Show the applicant the current state") Drive your front end off the top-level `status`. The create-submission response needs only three screens: | `status` | UI action | | ----------- | ------------------------------------------------------------------------------------------------------------ | | `succeeded` | Show the success screen – the applicant is done. | | `failed` | Show failure messaging (see [failure reasons](#handle-common-failure-reasons)). | | `pending` | Show an "Application is being reviewed" screen, or prompt for a document if your configuration requires one. | During your implementation, Goodstack will consult with you on your configuration and let you know whether your checks require documents. If they do, prompt for them when `status` is `pending`, regardless of the nested agent verification status. If your configuration doesn't require documents, you don't need to prompt for them at all. ### 5d. Upload supporting documents, if required[​](#5d-upload-supporting-documents-if-required "Direct link to 5d. Upload supporting documents, if required") When your configuration calls for documentation, your front end collects the file, and your backend receives it and forwards it to Goodstack with the submission ID. Your **backend** makes this call and authenticates with your **secret key**: ``` POST https://api.goodstack.io/v1/validation-submission-documents Authorization: Idempotency-Key: ver_attempt_01HZR9YJ6K8X4R7Q2N5P3M1A0B_document_1 Content-Type: multipart/form-data validationSubmissionId=validationsubmission_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx file= ``` Use a unique `Idempotency-Key` for each document upload, and reuse it only when retrying the same file upload. Once the document is uploaded, Goodstack's verification team reviews it. You receive a `validation_submission.succeeded` or `validation_submission.failed` webhook once the review is complete. Next, see [Shared reference](#7-shared-reference) for status meanings, document rules, webhook events, and failure reasons. ## 6. Direct browser integration[​](#6-direct-browser-integration "Direct link to 6. Direct browser integration") Choose this path when applicant documents should go straight from the browser to Goodstack. Your backend still creates the session, but document bytes never touch your servers. In the direct browser flow: * Your backend creates a validation invite with your **secret key**. * Your front end uploads any applicant document with your **publishable key** and `validationInviteId`. * Your front end creates the validation submission with your **publishable key** and the same `validationInviteId`. * Your backend receives the final result by webhook. ### 6a. Create a validation invite[​](#6a-create-a-validation-invite "Direct link to 6a. Create a validation invite") Your backend starts each verification session by creating a **validation invite** with the [Create Validation Invite endpoint](/docs/api/create-a-validation-invite.md). The invite is a browser-safe correlation token: it lets your front end upload documents and create the submission directly against Goodstack with your publishable key, while your secret key stays on your server and document bytes stay out of your infrastructure. ``` POST https://api.goodstack.io/v1/validation-invites Authorization: Content-Type: application/json { "hostedConfigId": "hostedconfiguration_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "language": "en-US", "metadata": { "applicantId": "user_123" } } ``` The `hostedConfigId` references the hosted configuration that defines which checks run and which institution types qualify. It plays the same role as the `configurationId` in the server-side integration. You receive it during onboarding. Goodstack responds with a `validationInviteId`. Return **only** the `validationInviteId` to your front end – it is safe to expose in the browser. Keep applicants in your own UI The invite response may include a hosted form URL because validation invites can also be used with Goodstack-hosted forms. In this direct browser flow, you do not need to redirect applicants. Pass the `validationInviteId` to your browser so it can upload documents and create the submission. ### 6b. Upload applicant documents up-front[​](#6b-upload-applicant-documents-up-front "Direct link to 6b. Upload applicant documents up-front") Your front end uploads the file straight to Goodstack **before** creating the submission, using the `validationInviteId`. Send the request as `multipart/form-data`. ``` POST https://api.goodstack.io/v1/validation-submission-documents Authorization: Idempotency-Key: ver_attempt_01HZR9YJ6K8X4R7Q2N5P3M1A0B_document_1 Content-Type: multipart/form-data validationInviteId=validationinvite_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx documentType=agent_verification file= ``` Multipart fields: | Field | Required | Description | | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `validationInviteId` | Yes | The invite ID your backend created in [step 6a](#6a-create-a-validation-invite) | | `file` | Yes | The document the applicant selected | | `documentType` | No | `validation_request` (default) for evidence about the institution; `agent_verification` for evidence about the applicant (e.g. a student ID or payslip) | For an education flow, most applicant-uploaded documents are evidence about *them* – such as a student ID, enrolment letter, school email evidence, teacher payslip, or letter from the school – so use `agent_verification`. Use `validation_request` only for evidence about the institution itself. Collect documents up-front Collecting documentation up-front is the recommended direct browser path and gives the highest verification success rates – the document is already attached when the checks run, so you never have to bring the applicant back into the flow to provide more evidence. ### 6c. Create the validation submission[​](#6c-create-the-validation-submission "Direct link to 6c. Create the validation submission") After any document uploads are complete, your front end creates the submission directly with the [Create Validation Submission endpoint](/docs/api/create-a-validation-submission.md). Use the **publishable key** and pass the `validationInviteId` instead of `configurationId`. If the applicant selected an institution from search results, pass `organisationId`: ``` POST https://api.goodstack.io/v1/validation-submissions Authorization: Idempotency-Key: ver_attempt_01HZR9YJ6K8X4R7Q2N5P3M1A0B_submission Content-Type: application/json { "validationInviteId": "validationinvite_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "organisationId": "organisation_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "firstName": "Alex", "lastName": "Doe", "email": "alex.doe@greenfield.edu", "language": "en-US", "metadata": { "applicantId": "user_123" } } ``` If the applicant entered institution details manually, replace `organisationId` with the manual institution fields from [step 3d](#3d-handle-found-and-manual-institutions). The applicant fields are otherwise identical. Creating the submission completes the invite: Goodstack attaches any documents already uploaded against the `validationInviteId` to the new submission, and you cannot reuse the invite afterwards. Goodstack responds with the same submission shape as the server-side integration. In the direct browser flow, `validationInviteId` is populated with the invite the submission was created from, and `metadata` echoes back what you sent. Metadata is the recommended way to match direct browser submissions to your own user records. Goodstack returns metadata with the submission on every read and in every webhook. Treat each invite as one verification attempt Store the `validationInviteId` against your applicant session before returning it to the browser, and prevent the browser from submitting the same invite more than once. Use a stable `Idempotency-Key` when the browser creates the submission, and retry with the same key and payload if the browser does not receive the response. If the attempt still cannot be confirmed, wait for the webhook or reconcile through support tooling before issuing a new invite. ### 6d. Show the applicant the current state[​](#6d-show-the-applicant-the-current-state "Direct link to 6d. Show the applicant the current state") The top-level `status` has the same meaning in both paths: | `status` | UI action | | ----------- | ------------------------------------------------------------------------------- | | `succeeded` | Show the success screen – the applicant is done. | | `failed` | Show failure messaging (see [failure reasons](#handle-common-failure-reasons)). | | `pending` | Show an "Application is being reviewed" screen. | In the direct browser flow, collect documents before submission if your configuration needs them. A validation invite stops accepting document uploads once you create its validation submission. Next, see [Shared reference](#7-shared-reference) for status meanings, document rules, webhook events, and failure reasons. ## 7. Shared reference[​](#7-shared-reference "Direct link to 7. Shared reference") ### Submission statuses[​](#submission-statuses "Direct link to Submission statuses") The `status` field will be one of: | Status | Meaning | | ----------- | ----------------------------------------------------------------------------- | | `succeeded` | All checks passed. The applicant qualifies. | | `failed` | One or more checks failed. The applicant does not qualify. | | `pending` | Checks are still running, or the applicant needs to provide more information. | When `status === 'pending'`, the nested `agentVerification.status` tells you where the applicant check stands. It's useful context, but **don't use it to decide whether to show a document-upload screen**. During your implementation, Goodstack will consult with you on your configuration and let you know whether your checks require documents. | `agentVerification.status` | Meaning | UI action | | --------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------ | | `approved` | Agent check passed; other checks still running | Show "in review" screen | | `pending` | Agent check is still running | Show "in review" screen | | `pending_review` | Goodstack has queued the submission for manual review | Show "in review" screen | | `pending_user_verification` | Goodstack is reaching out to the applicant directly | Show "in review" screen | | `rejected` | Agent verification failed | Show failure messaging (see [failure reasons](#handle-common-failure-reasons)) | ### Document requirements[​](#document-requirements "Direct link to Document requirements") Send documents to the [Create Validation Submission Document endpoint](/docs/api/create-validation-submission-document.md) as a `multipart/form-data` request. | Integration | Auth | Linked by | When to upload | | -------------- | ------------------------- | ------------------------ | ---------------------------------------------------------------------------- | | Server-side | Secret key (backend) | `validationSubmissionId` | After the submission exists, when your configuration calls for documentation | | Direct browser | Publishable key (browser) | `validationInviteId` | Before you create the submission | Both modes accept `jpg`, `png`, and `pdf` files up to **5 MB**. Goodstack validates file content, not the label Goodstack identifies the file type by reading the file's actual **content** – not its `Content-Type` header or file extension. The API will reject any file that is not a real JPEG, PNG, or PDF inside, even one labelled as such: ``` { "error": { "code": "upload/filetype_not_supported", "title": "Bad request", "message": "Filetype not supported. Supported types include: jpg,jpeg,png,pdf" } } ``` The most common cause is a file whose label doesn't match its contents – for example iPhone photos, which are often HEIC files labelled `image/jpeg`. To avoid rejected uploads: 1. Validate the file's real content on your side, not its label or extension. 2. If you can't identify the file, or it is another format (like HEIC), block it before sending – or convert it to JPEG/PNG first. 3. Keep files under 5 MB. Coaching the applicant The document the applicant uploads has a large impact on how quickly the verification completes. Give the applicant a short, specific list of acceptable proofs rather than a generic "upload a document" prompt: a student ID, an enrolment letter, official school correspondence, or – for teachers – a payslip showing the school name or a letter on school letterhead. ### Receive and process results[​](#receive-and-process-results "Direct link to Receive and process results") Verification checks resolve asynchronously, so we recommend using webhooks for result updates. Goodstack notifies your system when each submission resolves, which avoids polling and keeps your integration responsive. info To receive results, you will need the [Webhook Subscriptions API](/docs/api/list-webhook-subscriptions.md). Use webhooks for state changes Use webhooks for state changes in both sandbox and production. Use the [Retrieve Validation Submission API](/docs/api/retrieve-a-validation-submission.md) for debugging, support tooling, reconciliation, or recovering from a missed webhook delivery – don't poll it as your primary path. ``` GET https://api.goodstack.io/v1/validation-submissions/validationsubmission_xxx Authorization: ``` It returns the same submission shape as the create call, reflecting the latest state: ``` { "data": { "id": "validationsubmission_xxx", "status": "succeeded", "organisationId": "organisation_xxx", "agentVerification": { "id": "agentverification_xxx", "status": "approved", "rejectionReasonCode": null }, "validationRequestId": "validationrequest_xxx", "metadata": {} }, "object": "ValidationSubmission" } ``` Before subscribing, create server-side event handlers that trigger any required actions (account upgrades, follow-up emails, etc.). When your handlers are ready, register them via the [Create Webhook Subscription endpoint](/docs/api/create-a-webhook-subscription.md). info Process webhooks quickly and return a `200` response as soon as you have verified and stored the event. If your handler is slow, Goodstack times out and retries. Use a queue if your processing is non-trivial. Verify that an incoming webhook came from Goodstack using the standard signature-verification pattern. The [Verifying webhooks](/docs/concepts/Platform/webhooks.md#verifying-webhooks) section of the Webhooks concept page has a complete code example and details on the `Goodstack-Signature` header. ### Events to subscribe to[​](#events-to-subscribe-to "Direct link to Events to subscribe to") The [events](/docs/api/goodstack-services.md#validation-submission-events) for Education verification are: * `validation_submission.created` – fired as soon as you successfully POST to the create endpoint * `validation_submission.succeeded` – all checks in the configuration passed * `validation_submission.failed` – one or more checks failed * `validation_submission.updated` – fired when the outcome of an already-resolved submission changes (only if dynamic outcome changes are enabled on your account) ### Example payloads[​](#example-payloads "Direct link to Example payloads") **`validation_submission.created`** – sent when Goodstack creates a Validation Submission. ``` { "object": "event", "data": { "id": "event_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "eventType": "validation_submission.created", "createdAt": "2026-04-15T11:00:00.000Z", "eventData": { "id": "validationsubmission_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "status": "pending", "createdAt": "2026-04-15T10:30:00.000Z", "organisationId": "organisation_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "agentVerificationId": "agentverification_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "agentVerification": { "id": "agentverification_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "firstName": "Alex", "lastName": "Doe", "email": "alex.doe@greenfield.edu", "status": "pending", "rejectionReasonCode": null }, "eligibilitySubscriptionId": "eligibilitysubscription_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "validationRequestId": "validationrequest_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "validationRequest": { "id": "validationrequest_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "name": "Greenfield University", "acceptedAt": null, "rejectedAt": null, "organisationTypes": [] }, "monitoringSubscriptionId": null, "validationInviteId": null, "partnerFields": {}, "metadata": {} } } } ``` In the direct browser flow, `validationInviteId` is populated with the invite the submission was created from. **`validation_submission.failed`** – sent when a submission moves to the `failed` state. The `failureReasons` array tells you which checks failed and why. ``` { "object": "event", "data": { "id": "event_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "eventType": "validation_submission.failed", "createdAt": "2026-04-15T11:10:00.000Z", "eventData": { "id": "validationsubmission_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "status": "failed", "agentVerification": { "status": "rejected", "rejectionReasonCode": "fake_email_used" }, "failureReasons": [ { "check": "agent_verification", "reason": { "rejectionReasonCode": "fake_email_used" } }, { "check": "eligibility", "reason": { "status": "live", "results": { "eligibilityStatus": "fail" } } } ] } } } ``` The `validation_submission.succeeded` payload has the same shape with `status: "succeeded"` and no `failureReasons` array. ### Handle common failure reasons[​](#handle-common-failure-reasons "Direct link to Handle common failure reasons") When a submission fails, the `failureReasons` array breaks down which checks failed and gives a machine-readable reason. The most common reasons your front end should handle: | Rejection Reason Code | What it means | What to surface to the applicant | | --------------------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | | `fake_email_used` | We could not verify the applicant's email as belonging to the institution | "We could not verify your school email. Please reapply using a valid school-issued address." | | `validation_request_failed` | We could not confirm the institution is a legitimate educational organisation | "We could not verify your school. Please double-check the school details or contact support." | | `user_verification_expired` | The applicant did not respond to a verification step in time | "Your application has expired. Please reapply." | | `other` | A reason that does not map to a specific code (review the webhook payload) | Generic failure messaging; consider logging for review | Always check the full `failureReasons` array – a submission can fail for more than one reason. ## Next steps[​](#next-steps "Direct link to Next steps") * **API reference** – the [Create Validation Submission](/docs/api/create-a-validation-submission.md) and [Create Webhook Subscription](/docs/api/create-a-webhook-subscription.md) endpoints cover every field used in this guide. * **Webhooks in depth** – the [Webhooks concept page](/docs/concepts/Platform/webhooks.md) covers signature verification, retries, and subscription management. * **How the checks work** – the [Eligibility Check](/docs/concepts/Verifications/Checks/eligibility-review.md) and [Agent Verification](/docs/concepts/Verifications/Checks/agent-verification.md) concept pages explain what runs behind each submission. * **Hosted flow** – if you'd rather not build the UI, contact your account manager about Goodstack's hosted verification flow. --- # Monetary Donations – Overview The Goodstack API enables you to leverage Goodstack’s global donation infrastructure in your product. You can ether fully integrate Goodstack into your product [using our direct APIs]() or save time by using the [Goodstack Hosted Donation Gateway](). You can learn more about the two integration options below. ## Integration options[​](#integration-options "Direct link to Integration options") * [API setup](): Build a custom donation experience into your product, while handling the payments yourself * [Hosted setup](): Let our Hosted Donation Gateway do the hard work and get your donation experience set up in a few hours. ### Nonprofit validation[​](#nonprofit-validation "Direct link to Nonprofit validation") info You can learn all about how we validate nonprofits in the [nonprofit validation overview](/docs/concepts/Verifications/Checks/validations-overview.md) page We offer rigorous nonprofit compliance checks to ensure that all the nonprofits in our database are valid, compliant and meet our customers' eligibility criteria. --- # Donations API setup The API setup for monetary donations allows you to build a totally custom donation experience that will live in your product. If you don’t need a custom setup and are limited in engineering resource please have a look at our [hosted setup](). With our API only setup, you simply create donations using the [Donations API](/docs/api/create-a-donation.md) and Goodstack will keep track of your donations, request payment from you and handle timely disbursement of the donations to the nonprofits. There are many ways to build donations into your product including one-off donations, round ups and recurring donations. Below we will outline a typical flow for allowing users to make one off donations to the nonprofit of their choice. ## 1. Get your API keys[​](#1-get-your-api-keys "Direct link to 1. Get your API keys") info Contact your account manager to get access to the dashboard. Before you start your implementation, you’ll need access to the dashboard to retrieve your Goodstack API keys. Go to the [Keys page in your Partner dashboard](https://partners.goodstack.io/keys) to access your Publishable Key as well as your Secret Key. ![API Keys](/assets/images/Developer_Keys-2771971bc58c981a4e0951f6f96c1eb0.png) Authenticate your API requests by using these API keys in the request header. The secret key should be used for server to server requests and the publishable key for requests via your front end to any public API services. Header parameter name: `Authorization` Please do not publish your private key in repos or use anywhere in front end. ## 2: Find nonprofit[​](#2-find-nonprofit "Direct link to 2: Find nonprofit") info For this step, you’ll need the [Search Organisations API](/docs/api/search-organisations.md) Guide your users to find their nonprofit through your frontend application. Call the [Search Organisations API](/docs/api/search-organisations.md) from your frontend application using your publishable API key and display the results back to your users. The nonprofit search API can be filtered by: * Country using the countryCode parameter * Nonprofit name using the query parameter ## 3: Create donations on behalf of your users[​](#3-create-donations-on-behalf-of-your-users "Direct link to 3: Create donations on behalf of your users") info For creating Donations, you’ll need the [Donations API](/docs/api/create-a-donation.md) Optional: create a User to track multiple donations against one of your users. You can create a user using the [Users API](/docs/api/create-a-user.md) and submit the `userId` when creating a Donation. After your user has selected a nonprofit, create a donation using the [Donations API](/docs/api/create-a-donation.md), supplying the `organisationId` of the nonprofit as well as the `amount` and `currency`, from your backend application using your secret API key. ``` POST https://api.goodstack.io/v1/donations Authorization: Secret Key Content-Type: application/json { "organisationId": "organisation_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "amount": 1000, "currency": "AUD", "userId": "user_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } ``` On successful response of creating a Donation, you can update the user that they have successfully donated. ## 4. Track donations[​](#4-track-donations "Direct link to 4. Track donations") You can view and retrieve donations either in your dashboard (see image below) or using the [Donations API](/docs/api/retrieve-a-donation.md). ![Donations dashboard](/assets/images/Donations-875a2e3a910a4d131ff700005d6f0c76.png) You can query for Donations by `userId` and filter by date. A Donation object tracks the lifecycle of a donation. For a full reference see [Donations API](/docs/api/retrieve-a-donation.md). #### Donation lifecycle[​](#donation-lifecycle "Direct link to Donation lifecycle") Donations created through the [Donations API](/docs/api/create-a-donation.md) will be of type `direct` and may enter the following statuses: * `ACTIVE` → The donation may be edited or cancelled through the [Edit a Donation](/docs/api/edit-a-donation.md) and the [Cancel a Donation](/docs/api/cancel-a-donation.md) endpoints * `REQUESTED_PAYMENT` → Goodstack has requested payment of the donation * `RECEIVED_PAYMENT` → Goodstack has received payment of the donation * `DISBURSED` → the donation has been successfully paid out to the nonprofit * `CANCELLED` → when the donation has been cancelled via the API --- # Donations hosted gateway setup The Goodstack Hosted Donation Gateway enables you to create a branded donation experience in your product, using only a few lines of code. Once live, your users can easily and efficiently donate to the nonprofit of their choice whilst feeling like they’re in your application. You’ll be able to customize the following: ![Customization options](/assets/images/Hosted_donation_gateway-e02f5a183a1b243a628c01c0c87913a9.png) Here’s a summary of how the hosted donation flow will work: 1. Create a new Donation Session through Goodstack's API 2. Goodstack API returns unique URL to a hosted donation screen 3. Donation is submitted with user's donation amount and payment details 4. Users are redirected to your application 5. Users will receive a donation receipt via email ### Implementation process[​](#implementation-process "Direct link to Implementation process") ### 1. Get your API keys[​](#1-get-your-api-keys "Direct link to 1. Get your API keys") info Contact your account manager to get access to the dashboard. Before you start your implementation, you’ll need access to the dashboard to retrieve your Goodstack API keys. Go to the [Keys page in your Partner dashboard](https://partners.goodstack.io/keys) to access your Publishable Key as well as your Secret Key. ![Developer keys](/assets/images/Developer_Keys-2771971bc58c981a4e0951f6f96c1eb0.png) Authenticate your API requests by using these API keys in the request header. The secret key should be used for server to server requests and the publishable key for requests via your front end to any public API services. Header parameter name: `Authorization` warning Please do not publish your private key in repos or use anywhere in front end. ### 2: Customize the Hosted Donation Gateway[​](#2-customize-the-hosted-donation-gateway "Direct link to 2: Customize the Hosted Donation Gateway") You can customize the look and feel of the Hosted Donation Gateway: * A CSS hex colour value representing the primary branding colour for the hosted form e.g. `#EB3434` * A logo or icon that will be displayed on the form, in the event that we don’t have a logo for the non profit. For best results, we recommend that your logo is a square image larger than 128 x 128 pixels. JPG, JPEG and PNG file types are supported. You can’t currently customize this yourself (we’re working on it) but in the meantime you can share these with your account manager. ![Hosted Donation Gateway](/assets/images/Select_payment_method_Credi_card-de0bd20c47011d33560592b144ae9539.png) ### 3: Implement the hosted donation gateway[​](#3-implement-the-hosted-donation-gateway "Direct link to 3: Implement the hosted donation gateway") info It takes \~2h to set up the hosted donation gateway Add or update an endpoint on your server to create a donation session using the [Create A Donation Session API](/docs/api/create-a-donation-session.md) using your secret API key. A donation session represents your user’s session as they donate to a nonprofit. We recommend creating a new donation session each time your user attempts to donate. #### Donation session lifecycle[​](#donation-session-lifecycle "Direct link to Donation session lifecycle") The donation session object may enter the following statuses: * `open` → the donation session object will be in an `open` status after creation * `completed` → after a donation is successfully made by the user the status of the donation session object will transition to `completed` * `expired` → if no donation is completed before the `expiresAt` time the status will be expired. The `expiresAt` time is 24 hour after creation after which the user will be unable to complete a session and users loading the donation session will see a message saying the session is expired #### Create a donation session[​](#create-a-donation-session "Direct link to Create a donation session") To create a donation session you must supply the `organisationId` of the nonprofit you want your user to be able to donate to. To get the `organisationId` you can use the [Search Organisations API](/docs/api/search-organisations.md), either to build a search experience for your users or to find the nonprofits you want your users to donate to and manually add them to your application. The nonprofit search API can be filtered by: * Country using the `countryCode` parameter * Nonprofit name using the `query` parameter Donation sessions can be created with the following optional parameters: * `userId` → to track donations against one of your users, you may create a user using the [Users API](/docs/api/create-a-user.md). Store the `userId` against your user and send this through when creating a donation session. * `successUrl` → URL that the hosted form redirects to after the flow is completed. If this is not provided a success screen will be displayed to the user after a successful donation. * `currency` → to set the currency that the payment will be taken in. If this is not provided then the currency will be set based on the user’s location if the language is supported, or it will default to USD. * `language` → to set the preferred language for us to present the form in. If this is not provided then the language will be set based on the user’s location if the language is supported, or it will default to English. ``` POST https://api.goodstack.io/v1/donation-sessions Authorization: Secret Key Content-Type: application/json { "organisationId": "organisation_000000C8xMwmi4C4aoTi6C5FJv0Po", "userId": "user_000000CQkBwyHXpzEIP6u9DQkMFTE", "successUrl": "https://example.com/donation-completed" } ``` #### Example response[​](#example-response "Direct link to Example response") You will receive a response matching the format of the example below. ``` { "data": { "id": "donationsession_000000CQkA3eOP0pRQowOVpXnktcm", "status": "open", "organisationId": "organisation_000000C8xMwmi4C4aoTi6C5FJv0Po" "userId": "user_000000CQkBwyHXpzEIP6u9DQkMFTE", "language": "en-GB", "successUrl": "https://example.com/donation-completed", "url": "https://donate.goodstack.io/donation-session/donationsession_000000CQkA3eOP0pRQowOVpXnktcm?token=krniofea...mkl", "createdAt": "2022-03-12T13:46:54.000Z", "expiresAt": "2022-03-12T14:46:54.000Z" }, "object": "donation_session" } ``` After creating the donation session, direct your user to the `url` for the hosted donation gateway returned in the response. Your user will receive an email receipt after a successful donation. ![Receipt Email](/assets/images/Receipt_email-432306c7c658712f1954fa241bfc93c6.png) ## 4. Track donations & donation sessions[​](#4-track-donations--donation-sessions "Direct link to 4. Track donations & donation sessions") You can view and retrieve donations either in your dashboard (see image below), or using the [Donations API](/docs/api/list-donations.md). You can also receive updates on the outcome of a donation session by subscribing to webhooks. ![Donations Dashboard](/assets/images/Donations-875a2e3a910a4d131ff700005d6f0c76.png) You can query for Donations by `userId` and filter by date. The Donation object tracks the lifecycle of a donation. Donations created from Donation Sessions will be of type `hosted`. The Donation object also contains the following fields that are collected from the user as they successfully donate via the Hosted Donation Gateway: * `firstName` → user’s first name * `lastName` → user’s last name * `email` → user’s email * `consentedToBeContactedByOrg` → whether the user consented to be contacted by the benefitting organization * `anonymous` → whether the user wants to share their donation publicly or not For a full reference see [Donations API](/docs/api/list-donations.md). #### Donation lifecycle[​](#donation-lifecycle "Direct link to Donation lifecycle") Donations of type `hosted` may enter the following statuses: * `REQUESTED_PAYMENT` → user has submitted a donation amount and we are waiting for the payment to be completed * `RECEIVED_PAYMENT` → the payment has been completed * `DISBURSED` → the donation has been successfully paid out to the nonprofit * `CANCELLED` → if the user abandoned the session after submitting a donation amount, the donation will be automatically cancelled after 24 hours ### Webhooks[​](#webhooks "Direct link to Webhooks") To receive information about the outcome of a donation in real time, you can subscribe to our [webhooks service](/docs/api/create-a-webhook-subscription.md). ``` POST https://api.goodstack.io/v1/webhook-subscriptions Authorization: Secret Key Content-Type: application/json { "events": [ "donation.hosted.payment_received" ], "url": "https://your.endpoint.com/webhooks" } ``` Once payment is received for a donation, we will submit a POST request to your designated webhook URL to inform you of the event. The event type will be `donation.hosted.payment_received` and the object will contain information about the donation. For a full reference see [Goodstack webhooks](/docs/api/goodstack-services.md#donation-events). ## 5. Test your integration[​](#5-test-your-integration "Direct link to 5. Test your integration") You can use the details on the pages below to test your integration. Use the nonprofit details for testing nonprofit search or creating donation sessions and the test card details for testing donations. * [Test nonprofit organisations](/docs/concepts/Donations/test-orgs.md) * [Test cards](/docs/concepts/Donations/test-cards.md) --- # Use cases Discover Goodstack use cases in this section. ##### [Impact programs](/docs/use-cases/impact-program.md) [Verify applications to your impact program with fast and flexible tools](/docs/use-cases/impact-program.md) [Read more](/docs/use-cases/impact-program.md) ##### [Fundraising](/docs/use-cases/fundraising.md) [Raise funds for your selected causes with donors from around the world](/docs/use-cases/fundraising.md) [Read more](/docs/use-cases/fundraising.md) ##### [Workplace giving](/docs/use-cases/workplace-giving.md) [Create impactful giving programs for your employees](/docs/use-cases/workplace-giving.md) [Read more](/docs/use-cases/workplace-giving.md) --- # Fundraising Online charitable donations and fundraising have become increasingly popular methods for organizations to raise funds for their causes. With the rise of social media and online platforms, it has become easier than ever for individuals to donate to their favorite charities and for organizations to reach a larger audience. Online fundraising is a convenient and efficient way to support causes that you care about and make a positive impact on the world. In this use case we'll look at considerations for building donation experiences to support nonprofits - either as a nonprofit organization or as an enterprise looking to support good causes through their products. ## Fundraising as a nonprofit[​](#fundraising-as-a-nonprofit "Direct link to Fundraising as a nonprofit") ### Challenges[​](#challenges "Direct link to Challenges") As a nonprofit, every donation is critical in helping you to keep delivering your mission but there are lots of challenges in fundraising successfully: * **International limitations**: some fundraising tooling offers poor international support and is optimized only in a small number of countries * **Fees**: donation platforms may take a large fee, reducing the amount that reaches your nonprofit and limiting the impact you are able to deliver * **Poor user experience**: bad design can decrease the trust of donors or increase donor drop-off during the donation process ### Types of fundraiser[​](#types-of-fundraiser "Direct link to Types of fundraiser") The simplest approach for fundraising is a simple donation button to accept payments from users at any time. To make the most of your donor's attention it's best to offer both one-time and recurring payments. You may also want to run a specific timed campaign to drive awareness of fundraising for a particular area of activity or response to an emergency situation. info [Get in touch](https://goodstack.io/get-in-touch) if you want to discuss building donation campaign pages with Goodstack's platform. ### Goodstack donation tools[​](#goodstack-donation-tools "Direct link to Goodstack donation tools") If you've [claimed your nonprofit on our Causes Portal](https://causes.goodstack.org/), you can use our Donation Form tools to quickly get up and running with donations on your website. ## Fundraising as an enterprise[​](#fundraising-as-an-enterprise "Direct link to Fundraising as an enterprise") There are lots of opportunities to drive donations to good causes as an enterprise, whether you're a B2B or B2C platform. ### Defining supported causes[​](#defining-supported-causes "Direct link to Defining supported causes") In your app, you'll want to present options to your users for which good causes they can donate to, whether those are nominated by your team members or championed by your workplace. Goodstack has a database of 8m+ nonprofits, which you can use to present a search UI for your users to select their chosen recipient. info Read the [Search Organisations](/docs/api/search-organisations.md) API documentation to see how to implement search in your platform. We recommend adding a filter by country code in your search interface to improve the relevancy of search results. If you want to offer a fixed list of supported causes then you can identify which charities should be in your portal and only offer those in your donation flow. The key identifier that you'll need to trigger a donation to the cause is the `organisationId`: Goodstack's unique ID for a nonprofit. You can use the search API above to retrieve the `organisationId`. If you know the official registry ID of the nonprofit (e.g. the EIN in the United States or local equivalent) then you can also filter results using the `registryId` field to make sure you've got the right one. You may also want to group nonprofits into categories according to your internal criteria, for example those that are relevant to a particular impact metric for your company. Once you've identified the relevant nonprofits, you can use our Categories API to [build your list of categories](/docs/api/create-a-category.md) and [assign nonprofits to them](/docs/api/add-a-organisation-to-a-category.md) for easy retrieval in your platform. You can also [assign images to categories](/docs/api/set-an-image-for-a-category.md) so you can fetch relevant assets in your UI without needing to hard-code them. ### Types of donation opportunity[​](#types-of-donation-opportunity "Direct link to Types of donation opportunity") There are many ways you can choose to build giving into your product, depending on what you want to achieve. Below are three example use cases we sometimes help power: * **Direct donation from product**: offer an opportunity for your platform users to make a direct donation via our [donation gateway]() within your app or platform. * **Roundups in app**: calculate a roundup amount at the checkout and [create a donation](/docs/api/create-a-donation.md) for the rounded up value when your customers make a purchse. * **Percentage at checkout**: give your customers the option to select a percentage add-on to the value of their purchase and [create a donation](/docs/api/create-a-donation.md) for the corresponding amount. --- # Impact programs Impact programs can support eligible good causes, through software discount and credit programs that can help them save money on the software they need for their day-to-day operations. These programs are designed to make it easier and more affordable for causes to access the tools and resources they need to make a positive impact in their communities. In this use case, we'll explore some of the considerations you may want to look at in running or establishing an impact program, and how Goodstack can help. ## Eligibility[​](#eligibility "Direct link to Eligibility") First decide what types of organization should be eligible for your impact program. Many enterprise product offers are targeted to nonprofits (i.e. US 501(c)(3) companies or their equivalent around the world). See [Goodstack's definition of nonprofits](https://goodstack.org/nonprofit-definitions/) in different countries around the world. In addition to a nonprofit program, you may want to consider offers for other types of organization, such as Education institutions, Social Impact Organizations or other categories. info Get in touch to discuss your requirements if you have any questions about how Goodstack can support your program criteria You may also wish to set criteria around the areas of activity of organizations applying to your program, for example excluding nonprofits with political or religious affiliation, or ensuring that nonprofits working in education apply to your Education program. Goodstack tags causes with over 100 activity categories so you can have granular control over which causes should be eligible for your program and which should be excluded. Lastly, you may wish to restrict applications to organizations from certain countries. Goodstack does not permit applications from [globally sanctioned countries](https://goodstack.org/nonprofit-definitions/#:~:text=Ineligible%20countries) but other limitations are up to you to define. ## Product offers[​](#product-offers "Direct link to Product offers") There are several options to provide value to good causes through different types of product offer. Here are a few of the most common: ### Free tier[​](#free-tier "Direct link to Free tier") Allow free use of your product for verified nonprofits. This could be up to a maximum number of seats or only with a standard set of features available. ### Discount[​](#discount "Direct link to Discount") Offer a percentage reduction or flat discount to your standard pricing. While this may have a lower signup rate than for a free tier, users may make more active use of the product and linking to revenue can make your impact program more sustainable. ### Usage credit[​](#usage-credit "Direct link to Usage credit") Add a one-off or recurring credit to an account. This can also incentivise product usage but may be more susceptible to attempted fraud (see below). ## Application flow[​](#application-flow "Direct link to Application flow") Once you have defined the criteria and benefits of your program, it's time to figure out how to manage applications from prospective users. Choose the implementation that fits the experience you want to own: * [Nonprofit verification API]() – build the application inside your product and control each client state. * [Hosted nonprofit verification]() – redirect applicants to a Goodstack-hosted form with less front-end work. ### Program landing page[​](#program-landing-page "Direct link to Program landing page") The simplest flow is an application form launched from your program's landing page. Goodstack's Hosted Validation Form is a convenient solution for this type of user experience. After completing the form, you may want to redirect the user to a page with onboarding information or an account creation flow. ### In-app flow[​](#in-app-flow "Direct link to In-app flow") If you require causes to have an account before applying for your offer, you may wish to build the application flow inside your app, potentially while users are in trial status. For this approach, you can build a completely embedded experience by using our Validation APIs, or use our Validation Invite API with attached metadata specifying user account details so you can easily automate upgrades after approval. ### Emails to applicants[​](#emails-to-applicants "Direct link to Emails to applicants") Because verification is asynchronous, email can help you notify applicants when their application changes. Goodstack can send configured outcome emails on your behalf, or your app can trigger its own messages after it reconciles a [validation-submission event](). Confirm outcome-email behavior and branded templates with your Goodstack implementation contact. ## Offer redemption[​](#offer-redemption "Direct link to Offer redemption") Once applications have been approved, you will need to redeem your product offer for your users. At very low volumes, you may be happy to log in to your Partner Dashboard to check for any new approved applications and manually upgrade users. ### Integration approaches[​](#integration-approaches "Direct link to Integration approaches") For higher volume programs, integrate your systems with Validation Submission webhooks and an authoritative reconciliation read. Follow the [Nonprofit verification API guide]() to build this flow. If you can't easily expose an endpoint to handle webhook notifications, you may wish to use common web automation tools, such as Zapier, Workato or Make to route outcomes to other systems of record. ### Discount codes[​](#discount-codes "Direct link to Discount codes") An alternative option is to send unique codes to approved applicants, which they can redeem in your app to apply a discount or claim credits. ## Fighting fraud[​](#fighting-fraud "Direct link to Fighting fraud") One important consideration in managing an impact program is combating fraudulent applications. Fraud can take many forms: ### Fraudulent status[​](#fraudulent-status "Direct link to Fraudulent status") Organisations may falsely claim to have nonprofit status or other attributes in order to try and qualify for your program. Our teams are expertly trained to review registration statuses worldwide and spot forged documents. ### Fraudulent association[​](#fraudulent-association "Direct link to Fraudulent association") An applicant may try to exploit your program by pretending to be associated with an eligible organization. Our Agent Verification check helps you ensure that applicants are genuinely associated with the organization they are applying with. ### Squatting or hacking records[​](#squatting-or-hacking-records "Direct link to Squatting or hacking records") Bad actors may try to take over websites or registry accounts in order to gain access to programs. Our team look out for risk factors to identify and block applications from suspicious accounts. info Get in touch with our team to talk through your requirements around fraud prevention --- # Workplace giving Workplace giving programs are initiatives that allow employees to donate to charitable organizations through their employer. These programs provide a convenient way for employees to support causes they care about while also encouraging a sense of community and social responsibility within the workplace. Workplace giving can take many forms, including payroll deductions, employer matching programs, and volunteer opportunities. By participating in workplace giving, employees can make a positive impact on their communities and beyond. In this use case we'll explore some of the options for businesses or software providers to build workplace giving solutions on top of Goodstack's platform. info Looking for a workplace giving solution without any engineering requirements? Get in touch to discuss your requirements. ## Managing giving opportunities[​](#managing-giving-opportunities "Direct link to Managing giving opportunities") In your giving platform, you'll want to present options to your employees for which good causes they can donate to, whether those are nominated by your team members or championed by your workplace. Goodstack has a database of 8m+ nonprofits, which you can use to present a search UI for your users to select their chosen recipient. info Read the [Search Organisations](/docs/api/search-organisations.md) API documentation to see how to implement search in your platform. We recommend adding a filter by country code in your search interface to improve the relevancy of search results. If you want to offer a fixed list of supported causes then you can identify which charities should be in your portal and only offer those in your donation flow. The key identifier that you'll need to trigger a donation to the cause is the `organisationId`: Goodstack's unique ID for a nonprofit. You can use the search API above to retrieve the `organisationId`. If you know the official registry ID of the nonprofit (e.g. the EIN in the United States or local equivalent) then you can also filter results using the `registryId` field to make sure you've got the right one. You may also want to group nonprofits into categories according to your internal criteria, for example those that are relevant to a particular impact metric for your company. Once you've identified the relevant nonprofits, you can use our Categories API to [build your list of categories](/docs/api/create-a-category.md) and [assign nonprofits to them](/docs/api/add-a-organisation-to-a-category.md) for easy retrieval in your platform. You can also [assign images to categories](/docs/api/set-an-image-for-a-category.md) so you can fetch relevant assets in your UI without needing to hard-code them. ## Ways for employees to donate[​](#ways-for-employees-to-donate "Direct link to Ways for employees to donate") Once you've defined which nonprofits your employees can donate to, there are different approaches in how to collect donations and specify which nonprofit should receive them. ### Direct donation[​](#direct-donation "Direct link to Direct donation") The most straightforward way is to use our hosted donation gateway to take a direct donation from an employee for a given nonprofit. To generate a donation session, you'll need the organisation ID above for the chosen nonprofit. info [Read more]() about using our Hosted Donation Gateway. ### Spend donation credits[​](#spend-donation-credits "Direct link to Spend donation credits") You may wish to offer donation credits as a benefit for your employees. In this case, the enterprise would be responsible for the transfer of funds, but the employee would choose the recipient nonprofit. For this case, we would recommend that you create a donation request for your employee via our [Donation API](). This creates a record of an intention to donate to the chosen nonprofit and we will issue a donation request to you to reconcile the payment. info You will need to manage the balance of any donation credits that your employees have and update them each time they make a donation. You can [retrieve the paid out status](/docs/api/retrieve-a-donation.md) of the donation from our API. ### Support a workplace fundraiser[​](#support-a-workplace-fundraiser "Direct link to Support a workplace fundraiser") If you are running a targeted fundraising campaign, you can attach a `metadata` value to your donations to mark them as being part of that campaign. Metadata is supported for both hosted gateway donations and donation requests via the API. ## Matched donations[​](#matched-donations "Direct link to Matched donations") Matched donations help your employees to increase the impact of their donations by matching them with funds from your business. To implement a matched funding option for your staff, you can [create additional donation requests](/docs/api/create-a-donation.md) for each direct donation that they make. You may want to set specific policies or limits on which donations are eligible for matched funding, for example: * A matched funding limit per employee * A total matched funding limit * Matched funding limited to specific nonprofits or campaigns ---