Overview
OAuth Authentication
We use OAuth 2.0 to secure our endpoints, so you'll need to submit a request to get an application created.
Request OAuth API credentialsTo request access, please fill out the API access request form.
API Base URL
All API calls must be made against:
https://api.ccbchurch.com
Do not call https://[subdomain].ccbchurch.com directly.
Authentication Overview
Vendors and churches authorize access to a church's data with the three-legged OAuth 2.0 authorization code flow.
| Environment | Authorization URL | Token URL |
|---|---|---|
| Production | https://oauth.ccbchurch.com/oauth/authorize | https://api.ccbchurch.com/oauth/token |
| Sandbox | https://beta-oauth.ccbchurch.com/oauth/authorize | https://api-beta.ccbchurch.com/oauth/token |
Sandbox credentials requiredOnly sandbox-specific API credentials work with the sandbox servers, and production credentials only work in production.
System Auth
This is the required first step to gain access to a church's data. A Master Administrator, or someone with permission to edit system-wide settings, must authorize your application before you can access that church's data. This is a one-time step per church: after it, you keep access by refreshing tokens, not by asking the admin again.
ChMS admin with "edit system-wide settings" is requiredYou can learn about how to add the "edit system-wide settings" permission to your ChMS user account in this article https://support.pushpay.com/s/article/Getting-Started-Admin-Privileges#Who-Assign
Contact your ChMS master administrator. You can find the list of contacts from within ChMS in the Help & Support menu at the top-right of the screen. It will be listed as "Support: contact your church's support team"
Step 1 — Send the admin to the Authorization URL
Redirect the admin's browser (a GET) to the Authorization URL with these query string parameters.
| Parameter | Required? | Description |
|---|---|---|
client_id | Required | The OAuth client ID Pushpay issued for your application. Use your real client ID, not a placeholder from sample code. |
response_type | Required | Must always be code. |
redirect_uri | Required | Where to send the admin afterwards. Must match the redirect URI registered for your application character for character, including the scheme (https://, or http:// for localhost), port, path and any trailing slash. URL-encode it. |
state | Recommended | An unguessable value you generate per request. It comes back unchanged on the redirect so you can confirm the response belongs to a request you started (CSRF protection). |
subdomain | Optional | The church's ChMS subdomain (for https://mychurch.ccbchurch.com, use mychurch). Skips the step where the admin types it in. |
scope | Optional | Space-separated list of scopes. Must be a subset of the scopes granted to your application. If omitted, all granted scopes are requested. |
resource_owner_auth | Optional | Set to true to run Identity Auth instead of System Auth. Omit it for System Auth. |
This step isn't an API call from your server. Your app sends the admin's browser to a URL like this one, for example with a link, a button, or an HTTP 302 redirect:
GET https://oauth.ccbchurch.com/oauth/authorize
?client_id=YOUR_CLIENT_ID
&response_type=code
&redirect_uri=https%3A%2F%2Fapp.example.com%2Foauth%2Fcallback
&state=8f2c1e7a9b
&subdomain=mychurchThe admin then:
- enters the church subdomain, if you didn't send
subdomain - signs in to ChMS, if not already signed in
- reviews the requested scopes and approves your application
"Error accessing page — If you came here from a value added partner's site…"This page means the authorize request was rejected before sign-in, almost always because
client_idorredirect_uridoesn't match what is registered. No redirect back to your app happens in this case. Check for a placeholderclient_idfirst, then compareredirect_uriagainst the registered value exactly.
Step 2 — Receive the authorization code
You don't make a call in this step. After the admin approves, Pushpay redirects their browser to your redirect_uri, so your callback endpoint receives an incoming GET request. There's no JSON body: the values arrive as query string parameters on the URL.
https://app.example.com/oauth/callback
?code=AUTHORIZATION_CODE
&state=8f2c1e7a9b| Query parameter | Description |
|---|---|
code | The authorization code. You exchange it for tokens in Step 3. |
state | The same state value you sent in Step 1, returned unchanged. |
Your callback handler should read these two parameters, check that state matches the value you sent, and then make the Step 3 call from your server. The code is short-lived and single-use, so exchange it right away.
Step 3 — Exchange the code for tokens
POST to the Token URL from your server. Never do this from a browser or mobile app, because the request contains your client secret.
Headers
| Header | Value |
|---|---|
Accept | application/vnd.ccbchurch.v2+json |
Content-Type | application/x-www-form-urlencoded |
Authorization | Optional. Basic base64(client_id:client_secret), as an alternative to sending them in the body. |
Body parameters
| Parameter | Required? | Description |
|---|---|---|
grant_type | Required | Must be authorization_code. |
code | Required | The code value from Step 2. |
redirect_uri | Required | Must be exactly the same redirect_uri string you sent in Step 1. If it differs, the request fails and the code is used up, so you'll need to restart from Step 1. |
client_id | Required | Your OAuth client ID. Not needed in the body if you send a Basic Authorization header. |
client_secret | Required | Your OAuth client secret. Not needed in the body if you send a Basic Authorization header. |
You don't send subdomain here, because the code already identifies the church.
curl -X POST https://api.ccbchurch.com/oauth/token \
-H "Accept: application/vnd.ccbchurch.v2+json" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=authorization_code" \
--data-urlencode "code=AUTHORIZATION_CODE" \
--data-urlencode "redirect_uri=https://app.example.com/oauth/callback" \
--data-urlencode "client_id=YOUR_CLIENT_ID" \
--data-urlencode "client_secret=YOUR_CLIENT_SECRET"Response — 200 OK
{
"access_token": "{access token}",
"token_type": "bearer",
"expires_in": 7200,
"scope": "read:individuals read:groups",
"refresh_token": "{refresh token}"
}| Field | Description |
|---|---|
access_token | Send as the Bearer token on every API call. |
token_type | Always bearer. |
expires_in | Lifetime of the access token in seconds (7200 = 2 hours). |
scope | Space-separated scopes the token actually holds. This can be fewer than you asked for. |
refresh_token | Use it to get a new access token without involving the admin again. Store it securely. |
Step 4 — Call the API
Send the access token as a Bearer token on every request, together with the v2 Accept header:
curl https://api.ccbchurch.com/individuals \
-H "Authorization: Bearer {access token}" \
-H "Accept: application/vnd.ccbchurch.v2+json"Refreshing Tokens
Access tokens expire after two hours (expires_in: 7200). To get a new one, POST the refresh token to the same Token URL. Refresh tokens have no fixed expiry, but each one works only once.
Headers: same as the code exchange (Accept: application/vnd.ccbchurch.v2+json, Content-Type: application/x-www-form-urlencoded, optional Basic Authorization).
Body parameters
| Parameter | Required? | Description |
|---|---|---|
grant_type | Required | Must be refresh_token. |
refresh_token | Required | The most recent refresh_token you received. |
client_id | Required | Your OAuth client ID. Not needed in the body if you send a Basic Authorization header. |
client_secret | Required | Your OAuth client secret. Not needed in the body if you send a Basic Authorization header. |
scope is not accepted here. The new token keeps the scopes of the one it replaces.
curl -X POST https://api.ccbchurch.com/oauth/token \
-H "Accept: application/vnd.ccbchurch.v2+json" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=refresh_token" \
--data-urlencode "refresh_token={refresh token}" \
--data-urlencode "client_id=YOUR_CLIENT_ID" \
--data-urlencode "client_secret=YOUR_CLIENT_SECRET"Response — 200 OK
{
"access_token": "{new access token}",
"token_type": "bearer",
"expires_in": 7200,
"scope": "read:individuals read:groups",
"refresh_token": "{new refresh token}"
}
Refresh tokens rotate — always save the new oneEvery successful refresh returns a new
refresh_tokenand immediately revokes both the previous access token and the previous refresh token. If you keep using the old refresh token, you'll getinvalid_grantand need the admin to authorize again. If several workers share one token, make sure only one of them refreshes it, and save the new pair before anything else uses it.
A refresh also fails with invalid_grant if the church has removed your application's approval.
Token Errors
Token endpoint errors use the standard OAuth 2.0 error body:
{
"error": "invalid_client",
"error_description": "Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method."
}| HTTP | error | Common causes |
|---|---|---|
| 401 | invalid_request | Missing grant_type, code or refresh_token; the code is expired, already used, or unknown; redirect_uri doesn't match Step 1; the church's approval of your app is missing. |
| 401 | invalid_client | Missing client_id/client_secret, unknown client, or wrong secret (including mixing sandbox and production credentials). |
| 401 | invalid_grant | The refresh token is unknown, already used (rotated), or revoked; the church removed its approval; or the individual is no longer valid (see above). |
| 401 | unsupported_grant_type | grant_type isn't authorization_code or refresh_token. |
Additional Resources
- Refreshing access tokens (oauth.com)
- OAuth flow to get an access token
- Demystifying OAuth Security: State vs. Nonce vs. PKCE — background on OAuth security best practices
Updated 12 days ago
