As payments are made in Pushpay the information about those payments is made available via the Pushpay public API - a common need is to query for recent payments and then record them in a 3rd party system. This document serves as a guide to our API users who are looking to build a new integration using the API.
For guidance on how to handle rate limiting please see here.
A step-by-step process of how a payment synchronization generally works.
Authentication
https://pushpay.io/docs/security#oauth_code_flow
This involves redirecting to the authorize OAuth2 endpoint, requesting the scopes required by including a "scope" query string parameter with the following values (space separated):
Retrieve the list of in-scope merchants
https://pushpay.io/docs/operations#get__v1_merchants_in-scope
GET /v1/merchants/in-scope
For each merchant listing you will be able to:
See the list of additional fields configured for this listing.
{
"page": 0,
"pageSize": 25,
"total": 1,
"totalPages": 1,
"items": [
{
"homeCountry": "US",
"version": 123,
"key": "MTIzOkRUclhHb1Jtc24tX3NKMGxjZzJ3cUJqb1ZlTQ",
"handle": "demochurch",
"name": "Demo Church",
"address": "123 Summer grove",
"location": {
"latitude": -36.8567852,
"longitude": 174.7583516
},
"paymentParameters": {
"currency": "USD",
"payButtonLabel": "Pay",
"limits": {
"min": 10.0,
"max": 15000.0
},
"paymentPlaceholder": "e.g. 50.00",
"mustBePaidInSafari": false
},
"referenceDefinitions": [
{
"id": 3,
"order": 0,
"valueType": "Text",
"hasChoices": false,
"label": "Full Name",
"placeholder": "Full Name",
"isRequired": true,
"maxLength": 100
},
{
"id": 4,
"order": 0,
"valueType": "Email",
"hasChoices": false,
"label": "Email",
"placeholder": "Email",
"isRequired": true,
"maxLength": 100
},
{
"id": 123,
"order": 0,
"valueType": "SingleSelect",
"hasChoices": false,
"label": "Giving Type",
"isRequired": true,
"choices": [
{
"label": "General / Tithes",
"value": "1000",
"order": 0,
"isDefault": true
},
{
"label": "New Buildings",
"value": "1001",
"order": 1,
"isDefault": false
},
{
"label": "Staff Support",
"value": "1002",
"order": 2,
"isDefault": false
}
]
}
],
"_links": {
"self": {
"href": "https://api.pushpay.com/v1/merchant/MTIzOkRUclhHb1Jtc24tX3NKMGxjZzJ3cUJqb1ZlTQ"
},
"merchantsettlements": {
"href": "https://api.pushpay.com/v1/merchant/MTIzOkRUclhHb1Jtc24tX3NKMGxjZzJ3cUJqb1ZlTQ/settlements"
}
}
}
],
"_links": {
"self": {
"href": "https://api.pushpay.com/v1/merchants/in-scope"
}
}
}
Loop through the in-scope merchants.
https://pushpay.io/docs/operations/payments#get__v1_merchant_merchantKey_payments
GET /v1/merchant/{merchantKey}/payments?updatedFrom=2015-12-11T00:00:00Z&page=0
GET /v1/merchant/{merchantKey}/payments?updatedFrom=2015-12-11T00:00:00Z&page=1
GET /v1/merchant/{merchantKey}/payments?updatedFrom=2015-12-11T00:00:00Z&page=2
"_links": {
"self": {
"href": "https://api.pushpay.com/v1/merchant/MTpZc2M4M3hOM05KMmdxOHpDQklvYkxqQWpfY2M/payments?updatedFrom=2015-12-11T00:00:00Z&page=1"
},
"next": {
"href": "https://api.pushpay.com/v1/merchant/MTpZc2M4M3hOM05KMmdxOHpDQklvYkxqQWpfY2M/payments?updatedFrom=2015-12-11T00:00:00Z&page=2"
}
}
Donor Identification
https://pushpay.io/docs/operations/payments#get__v1_merchant_merchantKey_payments
In the payment, find the payer representation - which will have a key - this is the "pushpay account key":
"payer": {
"key": "MDoxWWpVN2dpTjNzeDdfMTdCcXk1bnZjOUJ5Qzg",
"emailAddress": "joe.bloggs@test.com",
"mobileNumber": "+15555555555",
"fullName": "Joe Bloggs"
...
},
This key can then be used to retrieve the pushpay account's details:
GET /v1/merchant/{merchantKey}/community/{memberKey}
And you can also set the exportKey using PATCH
PATCH /v1/merchant/{merchantKey}/community/{memberKey}
{
exportKey": "12343"
}
A step-by-step process of how a settlement batch import generally works.
Authentication
https://pushpay.io/docs/security#oauthcodeflow
This involves redirecting to the authorize OAuth2 endpoint, and requesting the scopes required for the payments integration by including a "scope" query string parameter with the following values (space separated):
Once the user consents to give your integration access you can retrieve the access token and refresh token. The refresh token will need to be stored somewhere secure. You can request new access tokens any time - so a good way to design your synchronization process is just to get a new access token prior to retrieving a processing new payments.
Retrieve the list of in-scope merchants
https://pushpay.io/docs/operations#get_v1merchants_in-scope
GET /v1/merchants/in-scope
This will:
For each merchant listing you will be able to:
See the list of additional fields configured for this listing - this will include the field which holds the fund information.
{
"page": 0,
"pageSize": 25,
"total": 1,
"totalPages": 1,
"items": [
{
"homeCountry": "US",
"version": 123,
"key": "MTIzOkRUclhHb1Jtc24tX3NKMGxjZzJ3cUJqb1ZlTQ",
"handle": "demochurch",
"name": "Demo Church",
"address": "123 Summer grove",
"location": {
"latitude": -36.8567852,
"longitude": 174.7583516
},
"paymentParameters": {
"currency": "USD",
"payButtonLabel": "Pay",
"limits": {
"min": 10.0,
"max": 15000.0
},
"paymentPlaceholder": "e.g. 50.00",
"mustBePaidInSafari": false
},
"referenceDefinitions": [
{
"id": 3,
"order": 0,
"valueType": "Text",
"hasChoices": false,
"label": "Full Name",
"placeholder": "Full Name",
"isRequired": true,
"maxLength": 100
},
{
"id": 4,
"order": 0,
"valueType": "Email",
"hasChoices": false,
"label": "Email",
"placeholder": "Email",
"isRequired": true,
"maxLength": 100
},
{
"id": 123,
"order": 0,
"valueType": "SingleSelect",
"hasChoices": false,
"label": "Giving Type",
"isRequired": true,
"choices": [
{
"label": "General / Tithes",
"value": "1000",
"order": 0,
"isDefault": true
},
{
"label": "New Buildings",
"value": "1001",
"order": 1,
"isDefault": false
},
{
"label": "Staff Support",
"value": "1002",
"order": 2,
"isDefault": false
}
]
}
],
"_links": {
"self": {
"href": "https://api.pushpay.com/v1/merchant/MTIzOkRUclhHb1Jtc24tX3NKMGxjZzJ3cUJqb1ZlTQ"
},
"merchantsettlements": {
"href": "https://api.pushpay.com/v1/merchant/MTIzOkRUclhHb1Jtc24tX3NKMGxjZzJ3cUJqb1ZlTQ/settlements"
}
}
}
],
"_links": {
"self": {
"href": "https://api.pushpay.com/v1/merchants/in-scope"
}
}
}
Fetch recent settlements
https://pushpay.io/docs/operations/settlements#get__v1_merchant_merchantKey_settlements
Fetch the settlements updated since last time your code checked.
GET /v1/settlements?updatedFrom=2015-12-11T00:00:00Z&page=0
The results are paged so you will need to keep fetching pages of settlements until you've read them all.
GET /v1/settlements?updatedFrom=2015-12-11T00:00:00Z&page=1
GET /v1/settlements?updatedFrom=2015-12-11T00:00:00Z&page=2
Each page of settlements details returned will include some links in the response for the next page, if there is one - using these links can simplify the process of paging in our API.
"_links": {
"self": {
"href": "https://api.pushpay.com/v1/merchant/MTpZc2M4M3hOM05KMmdxOHpDQklvYkxqQWpfY2M/settlements?updatedFrom=2015-12-11T00:00:00Z&page=1"
},
"next": {
"href": "https://api.pushpay.com/v1/merchant/MTpZc2M4M3hOM05KMmdxOHpDQklvYkxqQWpfY2M/settlements?updatedFrom=2015-12-11T00:00:00Z&page=2"
}
}
As an alternative, if it makes sense you can loop through individual merchant listings, retrieving the settlements per merchant listing (this is similar the payment import process described earlier in this document).
Loop through the in-scope merchants.
https://pushpay.io/docs/operations/settlements#get__v1_merchant_merchantKey_settlements
For each merchant you can retrieve the list of batches updated since the last time synchronization was performed (in UTC)
GET /v1/merchant/{merchantKey}/settlements?updatedFrom=2015-12-11T00:00:00Z&page=0
The results are paged so you will need to keep fetching pages of settlements until you've read them all.
GET /v1/merchant/{merchantKey}/settlements?updatedFrom=2015-12-11T00:00:00Z&page=1
GET /v1/merchant/{merchantKey}/settlements?updatedFrom=2015-12-11T00:00:00Z&page=2
Fetch the payments for a settlement.
In the details of each settlement is a link to the payments within a settlement ("settlementpayments") - fetching this will retrieve a paged list of payments.
{
"key": "MDpkQUFOQ1FzdE1BLVZfVWZFdEZkQ3dvb3YyTDg",
"name": "Settlement #1",
"totalAmount": {
"amount": "202.00",
"currency": "USD"
},
"type": "ACH",
"totalPayments": 2,
"estimatedDepositDate": "2016-01-03T07:00:00Z",
"isReconciled": true,
"_links": {
"self": {
"href": "https://api.pushpay.com/v1/settlement/MDpkQUFOQ1FzdE1BLVZfVWZFdEZkQ3dvb3YyTDg"
},
"settlementpayments": {
"href": "https://api.pushpay.com/v1/settlement/MDpkQUFOQ1FzdE1BLVZfVWZFdEZkQ3dvb3YyTDg/payments"
}
}
}
The payments are paged - you will need to retrieve all pages of payments to be able to see the entire list of payments.
GET /v1/settlement/{settlementKey}/payments?page=0
GET /v1/settlement/{settlementKey}/payments?page=1
GET /v1/settlement/{settlementKey}/payments?page=2
Synchronize Community
GET: https://pushpay.io/docs/operations/community#get__v1_merchant_merchantKey_community_memberKey
PATCH: https://pushpay.io/docs/operations/community#patch__v1_merchant_merchantKey_community_memberKey
If the system you are synchronizing with manages a list of people, the identifier for these people (sometimes referred to as person ID or envelope number) may be stored in Pushpay as the export Key (Your ID) of the community member. So in the payment, find the payer representation - which will have a key - this is the "pushpay account key"
"payer": {
"key": "MDoxWWpVN2dpTjNzeDdfMTdCcXk1bnZjOUJ5Qzg",
"emailAddress": "joe.bloggs@test.com",
"mobileNumber": "+15555555555",
"fullName": "Joe Bloggs"
...
},
This key can then be used to retrieve the pushpay account's details:
GET /v1/merchant/{merchantKey}/community/{memberKey}
And you can also set the exportKey using PATCH
PATCH /v1/merchant/{merchantKey}/community/{memberKey}
{
"exportKey": "12343"
}
If you are leveraging the "exportKey" (Your ID) field to store the ID of the individual in your church management system / CRM system - then you will need to update these in pushpay to reflect changes in the external system e.g. when a merge occurs. To assist with this we have an API which allows making changes to the community in response to external merges etc.
Authenticating
https://pushpay.io/docs/security#oauth_code_flow
Once the user consents to give your integration access you can retrieve the access token and refresh token. The refresh token will need to be stored somewhere secure. You can request new access tokens any time - so a good way to design your synchronization process is just to get a new access token prior to retrieving a processing new payments.
Get an access token using the refresh token
https://pushpay.io/docs/security#oauth_refresh_token
Retrieve the list of in-scope merchants
https://pushpay.io/docs/operations/merchants#get__v1_merchants_in-scope
GET /v1/merchants/in-scope
This will return the list of merchant listings your code can retrieve/manage the community for
{
"page": 0,
"pageSize": 25,
"total": 1,
"totalPages": 1,
"items": [
{
"homeCountry": "US",
"version": 123,
"key": "MTIzOkRUclhHb1Jtc24tX3NKMGxjZzJ3cUJqb1ZlTQ",
"handle": "demochurch",
"name": "Demo Church",
"address": "123 Summer grove",
"location": {
"latitude": -36.8567852,
"longitude": 174.7583516
},
"paymentParameters": {
"currency": "USD",
"payButtonLabel": "Pay",
"limits": {
"min": 10.0,
"max": 15000.0
},
"paymentPlaceholder": "e.g. 50.00",
"mustBePaidInSafari": false
},
"referenceDefinitions": [
{
"id": 3,
"order": 0,
"valueType": "Text",
"hasChoices": false,
"label": "Full Name",
"placeholder": "Full Name",
"isRequired": true,
"maxLength": 100
},
{
"id": 4,
"order": 0,
"valueType": "Email",
"hasChoices": false,5
"label": "Email",
"placeholder": "Email",
"isRequired": true,
"maxLength": 100
},
{
"id": 123,
"order": 0,
"valueType": "SingleSelect",
"hasChoices": false,
"label": "Giving Type",
"isRequired": true,
"choices": [
{
"label": "General / Tithes",
"value": "1000",
"order": 0,
"isDefault": true
},
{
"label": "New Buildings",
"value": "1001",
"order": 1,
"isDefault": false
},
{
"label": "Staff Support",
"value": "1002",
"order": 2,
"isDefault": false
}
]
}
],
"_links": {
"self": {
"href": "https://api.pushpay.com/v1/merchant/MTIzOkRUclhHb1Jtc24tX3NKMGxjZzJ3cUJqb1ZlTQ"
},
"merchantsettlements": {
"href": "https://api.pushpay.com/v1/merchant/MTIzOkRUclhHb1Jtc24tX3NKMGxjZzJ3cUJqb1ZlTQ/settlements"
}
}
}
],
"_links": {
"self": {
"href": "https://api.pushpay.com/v1/merchants/in-scope"
}
}
}
Update community with merges
https://pushpay.io/docs/operations/community#patch__v1_merchant_merchantKey_community_memberKey
You can do this by making a PATCH request to the community URL for each merchant listing (identified by it's merchant Key). The request allows you to make an "update" from an old "exportKey" (Your ID) to a new "exportKey" value. This is used when a individual in the 3rd party system has been merged, resulting in either a "winner" existing individual ID, or a brand new "merged" individual ID.
PATCH /v1/merchant/{merchantKey}/community
{
"operations": [
{
"update": {
"exportKey": {
"from": "1234",
"to": "4561"
}
}
},
{
"update": {
"exportKey": {
"from": "2312",
"to": "4561"
}
}
},
...
Bulk assign export Keys
https://pushpay.io/docs/operations/community#patch__v1_merchant_merchantKey_community_memberKey
{
"operations": [
{
"set": {
"key": "MDoxWWpVN2dpTjNzeDdfMTdCcXk1bnZjOUJ5Qzg",
"exportKey": "1234"
}
},
{
"set": {
"key": "ABIzOkRUclhHb1Jtc24tX3NKMGxjZzJ3cUJqb1ZlT9",
"exportKey": "1234"
}
},
...
If your integration needs to synchronize payments constantly, rather then in batches, you can do so by configuring a webhook (either via the Merchant Admin portal, or by using the webhooks API) https://pushpay.io/docs/operations#post__v1_merchant_merchantKey_webhooks. Once the webhook is configured, every time a payment is created or updated, a POST request will be sent to the URI you configured in the webhook. The body of the events are JSON:
{
"subscription": "http://api.pushpay.com/v1/webhook/token",
"events": [
{
"date": "2015-01-02T03:04:05Z",
"eventType": "payment_created",
"entityType": "Payment",
"links": {
"merchant": "http://api.pushpay.com/v1/merchant/MTIzOkRUclhHb1Jtc24tX3NKMGxjZzJ3cUJqb1ZlTQ",
"payment: "/v1/merchant/MTIzOkRUclhHb1Jtc24tX3NKMGxjZzJ3cUJqb1ZlTQ/payment/q235azs3KMGxjZzJ3cUJqb1349s0909"
}
}
]
}
No sensitive information is included in the webhook event - it will only contain links to the affected items which can be retrieved to find more details.
A common problem for systems synchronizing payments is how to deal with bank payments which may take up to 7 days to process. Within Pushpay these payments will be immediately visible, and have a status of "Processing". At the point they succeed (or return) the status will then change to "Success" or "Failed". This could take up to 7 days to happen. In some systems you may already have the concept of a "Processing" payment, in which case the 2 systems will align well. Otherwise, there are 2 options: * Not displaying the payment until it's status has changed to Success. However, this can lead to a lot of user confusion. * Show the payment immediately, but then "handle" the uncommon case of the payment failing, rather then succeeding. We refer to this as compensation - as soon as the payment has a status of processing, treat it as a successful payment in your system. If it later fails, either void the payment in your system- OR - create a second "compensating" payment that has all the same details but a negative amount (so it negates the original payment). By doing so you provide an experience which is much less confusing to merchant administrators.
Development is always done against the sandbox environment - Pushpay does not issue production API credentials until it has seen evidence of an integration working against the sandbox environment. Sandbox access can be requested (as well as general support for the API) by sending an email to api@pushpay.com To configure sandbox access we need to know: