Pawn Payment Solutions Gateway API
version: 1.1
Change Log
Introduction
With REST you specify the action you'd like to take with an HTTP verb (GET, POST, PATCH, DELETE) against a particular model with an HTTP request.
The gateway.pawn-pay.com REST API is divided into two sections:
The partner API and the merchant API.
The partner API is used for creating and managing merchants.
the merchant API API is used for creating and managing transactions.
Considerations
These are just some things you should keep in mind while preparing your integration.
Timestamps
All timestamps are in the UTC timezone and formatted asYYYY-MM-DD hh:mm:ss
Rate Limits
Each merchant/partner can only make so many requests per minute.Your current usage and limit can be found in the header of each response:
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 80
If you exceed your Rate Limit the server will return HTTP code 429 with the following headers
Retry-After: 12
time in seconds to waitX-RateLimit-Reset: 1571946053
unix timestamp when limit resetsNote: if you find yourself hitting the rate limit often consider using webhooks to get information from the gateway rather than polling for changes.
If you're still hitting the rate limits after that contact us and we can work together to either make your integration more efficient or increase your rate limits as needed.
API SDKs
We offer a PHP API SDK which can be downloaded from composer composer require pawnpay/merchant_api
Check it out on Packagist
We're working on SDKs for other languages. Stay tuned for that.
Authentication
To authenticate your request you must provide the partner/merchant ID and one of it's API keys in the Authorization
header of your request.
For partner endpoints use a partner ID and for merchant endpoints use a merchant ID.
The id and key must be base64 encoded like so `Basic base64(id:key)`
This should go without saying but your API Keys should be kept secret, out of client side code and out of any repositories you may have.
Authorization: "Basic dGVzdGluZzp0ZXN0aW5nMTIzNA=="
Request Validation
The bodies of POST and PATCH requests are validated for data integrity. Certain fields are required, others require a specific format. Requests that fail validation generate a 422 status response. The body of this response contains the details about why the request failed validation.
The message
attribute is a generic message about the failure.
The errors
attribute is an object whose keys are the fields which failed validation.
Each attribute in the errors
object is an array of strings, each string is a the message from a validation rule that failed.
{
"message": "The given data was invalid.",
"errors": {
"name": [
"The name field is required."
],
"email": [
"The email must be a valid email address."
],
"phone": [
"The phone must be a valid phone number."
]
}
}
Test Credentials
For testing purposes please use testing:testing1234
as your credentials. These work for both partner and merchant endpoints.
Any requests made with these credentials make no permanent changes in the partner API so you can feel free to play around with any partner endpoints.
This testing partner account has one merchant account associated with it.
Modifying this merchant will return a successful response (assuming your request passes validation) but not actually change the merchant.
For the merchant API
Payers, Authorizations and Transactions are created/updated normally.
Production Credentials
In order to receive your production credentials you must demonstrate your code follows our best practices by providing a copy of the code for the relevant portions of your integration.
At no point should you store any data covered by PCI DSS, encrypted or otherwise.
Your integration must have the following functionality:
- Create Payers
- Update Payers
- Delete Payers
- Create Payment Methods
- Update Payment Methods
- Delete Payment Methods
- Process Transactions
- Authorize Transactions
- Void Transactions
- Reverse Transactions
Once your integration has passed validation you will receive your production credentials
Partner API
Testing Authentication
GET
https://gateway.pawn-pay.com/api/v1/partner
You can use this endpoint to test your partner credentials. The server will response with the status 200 if the credentials are correct. 401 otherwise.
{
"message": "You're the man now dog!"
}
Create Merchant
POST
https://gateway.pawn-pay.com/api/v1/partner/merchants
Creating Merchants
field | required | validation |
---|---|---|
name | true | required, string, min:3, max:255 |
dba | true | required, string, min:3, max:255 |
ein | true | required, string, min:9, max:255 |
mcc | false | digits:4 |
website | true | required, url, max:255 |
description | true | required, string, min:15, max:255 |
merchant_descriptor | false | string, min:5, max:25, regex:/^[a-zA-Z0-9\s]+$/ |
true | required, email, max:55 | |
phone | true | required, phone, string, max:255 |
monthly_volume | true | required, integer, min:100000 |
average_ticket | true | required, integer, min:500 |
high_ticket | true | required, integer, min:1000 |
license | false | sometimes |
license.number | false | required_with:license, string, max:255 |
license.file | false | required_with:license, base64, base64_size:1024,KB |
license.file_type | false | required_with:license, |
address | true | required |
address.street | true | required, string, max:50 |
address.city | true | required, string, max:25 |
address.state | true | required, alpha, min:2, max:3 |
address.postal | true | required, alpha_num, min:5, max:10 |
address.country | true | required, alpha, size:3 |
mailing_address | false | sometimes |
mailing_address.street | false | required_with:mailing_address, string, max:50 |
mailing_address.city | false | required_with:mailing_address, string, max:25 |
mailing_address.state | false | required_with:mailing_address, alpha, min:2, max:3 |
mailing_address.postal | false | required_with:mailing_address, alpha_num, min:5, max:10 |
mailing_address.country | false | required_with:mailing_address, alpha, size:3 |
owners | true | required, array, min:1, max:5 |
owners.*.first | true | required, string, max:20 |
owners.*.last | true | required, string, max:25 |
owners.*.email | true | required, email, max:55 |
owners.*.phone | true | required, phone, string, max:255 |
owners.*.title | true | required, string, max:55 |
owners.*.ssn | true | required, string, size:9 |
owners.*.dob | true | required, date_format:m-d-Y |
owners.*.percentage | true | required, int, min:20, max:100 |
owners.*.address.street | true | required, string, max:50 |
owners.*.address.city | true | required, string, max:25 |
owners.*.address.state | true | required, alpha, min:2, max:3 |
owners.*.address.postal | true | required, alpha_num, min:5, max:10 |
owners.*.address.country | true | required, alpha, size:3 |
owners.*.license | false | sometimes |
owners.*.license.number | false | required_with:owners.*.license, string, max:255 |
owners.*.license.file | false | required_with:owners.*.license, base64, base64_size:1024,KB |
owners.*.license.file_type | false | required_with:owners.*.license, |
deposit_bank.account_name | true | required, string, min:3, max:32 |
deposit_bank.bank | true | required, string, min:3, max:50 |
deposit_bank.routing | true | required, string, size:9 |
deposit_bank.account | true | required, string, min:6, max:25 |
deposit_bank.type | true | required, |
deposit_bank.sub_type | true | required, |
deposit_bank.country | true | required, alpha, size:3 |
deposit_bank.voided_check | false | |
deposit_bank.voided_check.file | false | required_with:deposit_bank.voided_check, base64, base64_size:1024,KB |
deposit_bank.voided_check.file_type | false | required_with:deposit_bank.voided_check, |
deposit_bank.owner.first | true | required, string, max:20 |
deposit_bank.owner.last | true | required, string, max:22 |
deposit_bank.owner.address.street | true | required, string, max:50 |
deposit_bank.owner.address.city | true | required, string, max:25 |
deposit_bank.owner.address.state | true | required, alpha, min:2, max:3 |
deposit_bank.owner.address.postal | true | required, alpha_num, min:5, max:10 |
deposit_bank.owner.address.country | true | required, alpha, size:3 |
withdrawal_bank.account_name | false | required_with:withdrawal_bank, string, min:3, max:32 |
withdrawal_bank.bank | false | required_with:withdrawal_bank, string, min:3, max:50 |
withdrawal_bank.account | false | required_with:withdrawal_bank, string, min:6, max:25 |
withdrawal_bank.routing | false | required_with:withdrawal_bank, string, size:9 |
withdrawal_bank.type | false | required_with:withdrawal_bank, |
withdrawal_bank.sub_type | false | required_with:withdrawal_bank, |
withdrawal_bank.country | false | required_with:withdrawal_bank, alpha, size:3 |
withdrawal_bank.voided_check | false | |
withdrawal_bank.voided_check.file | false | required_with:withdrawal_bank.voided_check, base64, base64_size:1024,KB |
withdrawal_bank.voided_check.file_type | false | required_with:withdrawal_bank.voided_check, |
{
"name": "Johnny Inc.",
"dba": "Johnny's Test Emporium",
"description": "A brief description of the business up to 255 characters",
"merchant_descriptor": "JOHNNY INC",
"email": "j.test@example.com",
"phone": "9544941234",
"ein": "123456789",
"website": "http://www.johnnys.com/",
"monthly_volume": 1000000,
"average_ticket": 4000,
"high_ticket": 50000,
"address": {
"street": "1234 test street",
"city": "Townsville",
"state": "GA",
"postal": 30380,
"country": "USA"
},
"mailing_address": {
"street": "4321 johnnys street",
"city": "Townsville",
"state": "GA",
"postal": 30380,
"country": "USA"
},
"license": {
"number": "ABC12345",
"file": "TG9vayBhdCB5b3UsIGNvbWluZyBpbiBoZXJlIGFuZCBkZWNvZGluZyBteSBzdHJpbmcu",
"file_type": "jpg"
},
"owners": [
{
"first": "Johnathan",
"last": "Test",
"email": "j.test@example.com",
"phone": "9544944321",
"tile": "CEO",
"ssn": "123456789",
"dob": "06-24-1986",
"percentage": 80,
"address": {
"street": "1234 test street",
"city": "Townsville",
"state": "GA",
"postal": 30380,
"country": "USA"
},
"license": {
"number": "ABC12345",
"file": "TG9vayBhdCB5b3UsIGNvbWluZyBpbiBoZXJlIGFuZCBkZWNvZGluZyBteSBzdHJpbmcu",
"file_type": "jpg"
}
},
{
"first": "Michael",
"last": "Swifter",
"email": "m.swifter@example.com",
"phone": "9544942341",
"tile": "CTO",
"ssn": "987654321",
"dob": "01-02-1972",
"percentage": 20,
"address": {
"street": "1234 lean street",
"city": "Townsville",
"state": "GA",
"postal": 30380,
"country": "USA"
},
"license": {
"number": "ABC12345",
"file": "TG9vayBhdCB5b3UsIGNvbWluZyBpbiBoZXJlIGFuZCBkZWNvZGluZyBteSBzdHJpbmcu",
"file_type": "jpg"
}
}
],
"deposit_bank": {
"account_name": "Johnny Inc.",
"type": "business",
"sub_type": "checking",
"bank": "Bank o' Money",
"routing": "12345678",
"account": "87654321",
"country": "USA",
"owner": {
"first": "Johnathan",
"last": "Test",
"address": {
"street": "1234 test street",
"city": "Townsville",
"state": "GA",
"postal": 30380,
"country": "USA"
}
},
"voided_check": {
"file": "TG9vayBhdCB5b3UsIGNvbWluZyBpbiBoZXJlIGFuZCBkZWNvZGluZyBteSBzdHJpbmcu",
"file_type": "jpg"
}
},
"withdrawal_bank": {
"account_name": "Johnny Inc.",
"type": "business",
"sub_type": "checking",
"bank": "Bank o' Money",
"routing": "12345678",
"account": "87654321",
"country": "USA",
"voided_check": {
"file": "TG9vayBhdCB5b3UsIGNvbWluZyBpbiBoZXJlIGFuZCBkZWNvZGluZyBteSBzdHJpbmcu",
"file_type": "jpg"
}
}
}
Create Merchant Response:
{
"id": "9796e3a4-1ff9-4304-98f3-37c79bc32896",
"webhook_secret": "AOiz1LHRYY5iGv2rtqnRuNeNXrai7nyp",
"public_key": "aILBL9IqPdwcT8sVZsnn353h5TH7nsZd",
"api_key": "zq97W8ouVz2O1uh1P5lwQAgTUQU1OCFI",
"name": "Johnny Inc.",
"dba": "Johnny's Test Emporium",
"description": "A brief description of the business up to 255 characters",
"merchant_descriptor": "JOHNNY INC",
"website": "http://www.johnnys.com/",
"address": {
"street": "1234 test street",
"city": "Townsville",
"state": "GA",
"postal": 30380,
"country": "USA"
},
"mailing_address": {
"street": "4321 johnnys street",
"city": "Townsville",
"state": "GA",
"postal": 30380,
"country": "USA"
},
"owners": [
{
"first": "Johnathan",
"last": "Test",
"email": "j.test@example.com",
"phone": "9544944321",
"tile": "CEO",
"ssn": "123456789",
"dob": "06-24-1986",
"percentage": 80,
"address": {
"street": "1234 test street",
"city": "Townsville",
"state": "GA",
"postal": 30380,
"country": "USA"
}
},
{
"first": "Michael",
"last": "Swifter",
"email": "m.swifter@example.com",
"phone": "9544942341",
"tile": "CTO",
"ssn": "987654321",
"dob": "01-02-1972",
"percentage": 20,
"address": {
"street": "1234 lean street",
"city": "Townsville",
"state": "GA",
"postal": 30380,
"country": "USA"
}
}
]
}
Partner Webhooks
https://gateway.pawn-pay.com/api/v1/partner/webhooks
If you'd like to receive webhooks for events for ANY merchant you manage you can do so using the same as with the Merchant API, simply use the above URL as the base of your request instead of the Merchant API endpoint.
You'll use your partner webhook secret to validate incoming requests.
See the webhooks section for all the available actions.
Just remember to use https://gateway.pawn-pay.com/api/v1/partner/webhooks
as your base!
Merchant API
Testing Authentication
GET
https://gateway.pawn-pay.com/api/v1/merchant
You can use this endpoint to test your merchant credentials. The server will response with the status 200 if the credentials are correct. 401 otherwise.
{
"message": "You're the man now dog!"
}
Create Payer
POST
https://gateway.pawn-pay.com/api/v1/merchant/payers
Note: When creating a payer if there is already a payer with the same email
that existing payer will be returned with the status 200
If a new payer record was created the the status will be 201
field | required | validation |
---|---|---|
name | true | required, string, max:255 |
false | nullable, string, email, max:255 | |
phone | false | phone, string, max:255 |
address.street | false | string, max:50 |
address.city | false | string, max:25 |
address.state | false | alpha, min:2, max:3 |
address.postal | false | alpha_num, min:5, max:10 |
address.country | false | alpha, size:3 |
{
"name": "Johnny Test",
"email": "j.test@example.com",
"phone": "+19544941234",
"address": {
"street": "1234 test street",
"city": "Townsville",
"state": "GA",
"postal": 30380,
"country": "USA"
}
}
Create Payer response:
{
"id": "1bd2e600-acc6-443f-a796-8103458af1e7",
"name": "Johnny Test",
"email": "j.test@example.com",
"phone": "+19544941234",
"address": {
"street": "1234 test street",
"city": "Townsville",
"state": "GA",
"postal": 30380,
"country": "USA"
}
}
Update Payer
PATCH
https://gateway.pawn-pay.com/api/v1/merchant/payers/{payer_id}
Note: only pass the fields you want to update.
field | required | validation |
---|---|---|
name | false | string, max:255 |
false | string, email, max:255 | |
phone | false | phone, string, max:255 |
address.street | false | string, max:50 |
address.city | false | string, max:25 |
address.state | false | alpha, min:2, max:3 |
address.postal | false | alpha_num, min:5, max:10 |
address.country | false | alpha, size:3 |
{
"phone": "+19544944321",
"address": {
"street": "4321 test street",
"postal": 30381
}
}
Update Payer response:
{
"id": "1bd2e600-acc6-443f-a796-8103458af1e7",
"name": "Johnny Test",
"email": "j.test@example.com",
"phone": "+19544944321",
"address": {
"street": "4321 test street",
"city": "Townsville",
"state": "GA",
"postal": 30381,
"country": "USA"
}
}
Delete Payer
DELETE
https://gateway.pawn-pay.com/api/v1/merchant/payers/{payer_id}
Note: a successful response has the status 204
Payers with existing Transactions cannot be deleted.
{
"message": "deleted payer 1bd2e600-acc6-443f-a796-8103458af1e7"
}
Create Payment Method
POST
https://gateway.pawn-pay.com/api/v1/merchant/payers/{payer_id}/methods
The Pawn Payment Solutions Gateways supports 2 different types of payment methods: credit and ach.
Credit methods can be added in 2 different types: plain text and track data
Each type has different fields which are detailed below.
Payment Methods are associated with a particular Payer and can only be used with that Payer.
Note: a successful response has the status 201
Create Credit Method
POST
https://gateway.pawn-pay.com/api/v1/merchant/payers/{payer_id}/methods
field | required | validation |
---|---|---|
name | false | string, max:255 |
type | true | required, |
sub_type | true | required, |
account_name | true | required, string, max:255 |
address.street | false | string, max:50 |
address.city | false | string, max:25 |
address.state | false | alpha, min:2, max:3 |
address.postal | true | required, alpha_num, min:5, max:10 |
address.country | false | alpha, size:3 |
account | true | required, luhn |
exp | true | required, date_format:my, after:last day of previous month |
cvv | true | required, digits_between:3,4 |
{
"name": "Johnny's Visa",
"type": "credit",
"sub_type": "visa",
"account_name": "Johnny Test",
"account": "4242424242424242",
"exp": "0526",
"cvv": "123",
"address": {
"street": "1234 test street",
"city": "Townsville",
"state": "GA",
"postal": 30380,
"country": "USA"
}
}
Create credit Method response:
{
"id": "05fa04e9-807b-4a2a-b503-31437e803e3d",
"name": "Johnny's Visa",
"type": "credit",
"sub_type": "visa",
"account_name": "Johnny Test",
"last_four": "4242",
"exp": "0526",
"address": {
"street": "1234 test street",
"city": "Townsville",
"state": "GA",
"postal": 30380,
"country": "USA"
},
"payer_id": "1bd2e600-acc6-443f-a796-8103458af1e7"
}
Create ACH Method
POST
https://gateway.pawn-pay.com/api/v1/merchant/payers/{payer_id}/methods
field | required | validation |
---|---|---|
name | false | string, max:255 |
type | true | required, |
sub_type | true | required, |
account_name | true | required, string, max:255 |
address.street | false | string, max:50 |
address.city | false | string, max:25 |
address.state | false | alpha, min:2, max:3 |
address.postal | false | alpha_num, min:5, max:10 |
address.country | true | required, alpha, size:3 |
routing | true | required, digits:9 |
account | true | required, numeric |
{
"name": "Johnny's Checking",
"type": "ach",
"sub_type": "checking",
"account_name": "Johnny Test",
"routing": "061113415",
"account": "12341234",
"address": {
"street": "1234 test street",
"city": "Townsville",
"state": "GA",
"postal": 30380,
"country": "USA"
}
}
Create ACH Method response:
{
"id": "05fa04e9-807b-4a2a-b503-31437e803e3d",
"name": "Johnny's Checking",
"type": "ach",
"sub_type": "checking",
"account_name": "Johnny Test",
"last_four": "1234",
"address": {
"street": "1234 test street",
"city": "Townsville",
"state": "GA",
"postal": 30380,
"country": "USA"
},
"payer_id": "1bd2e600-acc6-443f-a796-8103458af1e7"
}
Create Track Method
POST
https://gateway.pawn-pay.com/api/v1/merchant/payers/{payer_id}/methods
Track methods can be created from the data that is gathered by card swiping terminals.
field | required | validation |
---|---|---|
name | false | string, max:255 |
type | true | required, |
sub_type | true | required, |
account_name | true | required, string, max:255 |
address.street | false | string, max:50 |
address.city | false | string, max:25 |
address.state | false | alpha, min:2, max:3 |
address.postal | false | alpha_num, min:5, max:10 |
address.country | false | alpha, size:3 |
track1 | true | required, string, hex |
track2 | true | required, string, hex |
ksn | true | required, string, hex |
device_type | true | required, |
exp | true | required, date_format:my, after:last day of previous month |
last_four | true | required, digits:4 |
{
"name": "Johnny's Visa",
"type": "track",
"sub_type": "visa",
"account_name": "Johnny Test",
"track1": "B3AC05A0D8B595F21C5BBCCEB382601279ED67D46A2FEB5AFBB26493C309BF5A050CA68BB30712977D8857C080A7F41251CAC18740EB3072",
"track2": "A718FFDBE97F343F792336D15BE6934EF5D17659796FCAB87A5768509C56B6DB23C3A2905CA2FD97",
"ksn": "9010240B154C5400059F",
"device_type": "MagTekDynamag",
"exp": "0526",
"last_four": "4242"
}
Create track Method response:
{
"id": "05fa04e9-807b-4a2a-b503-31437e803e3d",
"name": "Johnny's Visa",
"type": "credit",
"sub_type": "visa",
"account_name": "Johnny Test",
"last_four": "4242",
"exp": "0526",
"payer_id": "1bd2e600-acc6-443f-a796-8103458af1e7"
}
Get Payment Methods
GET
https://gateway.pawn-pay.com/api/v1/merchant/payers/{payer_id}/methods
Returns an array of the Payers stored Payment Methods.
Returns an empty array in the case of no Payment Methods.
[
{
"id": "05fa04e9-807b-4a2a-b503-31437e803e3d",
"name": "Johnny's Visa",
"type": "credit",
"sub_type": "visa",
"account_name": "Johnny Test",
"last_four": "4242",
"exp": "0526",
"address": {
"street": "1234 test street",
"city": "Townsville",
"state": "GA",
"postal": 30380,
"country": "USA"
},
"payer_id": "1bd2e600-acc6-443f-a796-8103458af1e7"
},
{
"id": "05fa04e9-807b-4a2a-b503-31437e803e3d",
"name": "Johnny's Checking",
"type": "ach",
"sub_type": "checking",
"account_name": "Johnny Test",
"last_four": "1234",
"address": {
"street": "1234 test street",
"city": "Townsville",
"state": "GA",
"postal": 30380,
"country": "USA"
},
"payer_id": "1bd2e600-acc6-443f-a796-8103458af1e7"
}
]
Update Payment Method
PATCH
https://gateway.pawn-pay.com/api/v1/merchant/methods/{method_id}
Note: For security reasons you may not update the account number(s), type or sub_type of a payment method, you must create a new method instead.
field | required | validation |
---|---|---|
name | false | string, max:255 |
account_name | false | string, max:255 |
exp | false | date_format:my, after:last day of previous month |
address.street | false | string, max:50 |
address.city | false | string, max:25 |
address.state | false | alpha, min:2, max:3 |
address.postal | false | alpha_num, min:5, max:10 |
address.country | false | alpha, size:3 |
{
"name": "Johnny's Awesome Visa",
"exp": "0527",
"address": {
"city": "Dankburge"
}
}
Update Payment Method response:
{
"id": "05fa04e9-807b-4a2a-b503-31437e803e3d",
"name": "Johnny's Awesome Visa",
"type": "credit",
"sub_type": "visa",
"account_name": "Johnny Test",
"last_four": "4242",
"exp": "0527",
"address": {
"street": "1234 test street",
"city": "Dankburge",
"state": "GA",
"postal": 30380,
"country": "USA"
},
"payer_id": "1bd2e600-acc6-443f-a796-8103458af1e7"
}
Delete Payment Method
DELETE
https://gateway.pawn-pay.com/api/v1/merchant/methods/{method_id}
Note: a successful response has the status 204
{
"message": "deleted method 05fa04e9-807b-4a2a-b503-31437e803e3d"
}
Client Side Tokenization
In order to avoid ever passing plaintext card data to your server you can encrypt and tokenize card data before sending it your server for processing.
Each Merchant has a public_key
which is safe for use in client side code and is used exclusively for this purpose.
Access this the Merchant's public key only grants the ability to tokenize cards, not charge them.
Note that the Merchant's public_key
is not a cryptographically secure value and is not use for encryption, only to authenticate requests.
This encryption/tokenization process works from any platform. Here we've provided examples in Javascript to run in web browsers.
// https://www.npmjs.com/package/node-forge
import forge from 'node-forge';
/**
* @param {string} message the thing you're encrypting
* @param {string} rsa_public_key the RSA public key acquired from /api/v1/public/tokenize/key
* @return {string}
*/
function encrypt(message, rsa_public_key)
{
// Random symmetric key
let sym_key = forge.random.getBytesSync(32);
// Encrypt message with symmetric key
const AES = forge.cipher.createCipher('AES-CBC', sym_key);
const iv = sym_key.substring(0, 16); // use first 16 as iv
AES.start({ iv });
AES.update(forge.util.createBuffer(message));
AES.finish();
// Base 64 encode message cipher
const cipher = forge.util.encode64(AES.output.data);
// Encrypt the symmetric key with the asymmetric key
rsa_public_key = `-----BEGIN PUBLIC KEY-----${rsa_public_key}-----END PUBLIC KEY-----`;
const key = forge.pki.publicKeyFromPem(rsa_public_key);
sym_key = key.encrypt(sym_key, 'RSAES-PKCS1-V1_5');
// Base 64 encode the symmetric key
sym_key = forge.util.encode64(sym_key);
// Encode length of symmetric key in hex
const len = sym_key.length.toString(16).padStart(3, '0');
// Concatenate hex length, symmetric key and message cipher
return `${len}${sym_key}${cipher}`;
}
function getRsaKey(merchant_public_key) {
const req_opts = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
public_key: merchant_public_key,
}),
};
const response = await fetch('https://gateway.pawn-pay.com/api/v1/public/tokenize/key', req_opts);
return await response.json();
}
function getMethodId(request) {
const req_opts = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify(request),
};
const response = await fetch('https://gateway.pawn-pay.com/api/v1/public/tokenize', req_opts);
return await response.json();
}
const MERCHANT_PUBLIC_KEY = 'aILBL9IqPdwcT8sVZsnn353h5TH7nsZd';
const rsa_key = getRsaKey(MERCHANT_PUBLIC_KEY);
const unencrypted_payload = {
payer: {
name: "Johnny Test",
email: "j.test@example.com",
phone: "+19544941234",
address: {
street: "1234 test street",
city: "Townsville",
state: "GA",
postal: 30380,
country: "USA",
},
},
payment_method: {
name: "Johnny's Visa",
type: "credit",
sub_type: "visa",
account_name: "Johnny Test",
account: "4242424242424242",
exp: "0221",
cvv: "123",
address: {
street: "1234 test street",
city: "Townsville",
state: "GA",
postal: 30380,
country: "USA",
},
},
};
const request_body = {
token_id: rsa_key.id,
payload: encrypt(JSON.stringify(unencrypted_payload), rsa_key.public_key),
};
const method = getMethodId(request_body);
// method.id
// method.payer_id
// can be safely passed to server and used to auth/process transactions
Get Public Key
POST
https://gateway.pawn-pay.com/api/v1/public/tokenize/key
To get a RSA public key to encrypt your client's symmetric key you must first request one from this endpoint.
Note: you have 1 hour from the time you generate the key to use the key, after that time the key is no longer valid.
Keys are good for only one successful request, you cannot reuse these keys.
The id
of the response is your RSA Key ID and the
public_key
is your RSA public key for the subsequent to Create Payer/Method
field | required | validation |
---|---|---|
public_key | true | required, string |
{
"public_key": "aILBL9IqPdwcT8sVZsnn353h5TH7nsZd"
}
Get Public Key response:
{
"id": "bv7fUdtTRSLP",
"public_key": "MIIBCgKCAQEA0U\/ulC+341gav3bITceYDBRB5Q0LYa\/rxfA8fVU8fnGBhtFcDi9OKfOMlgq4mO6wUs3R0tjLUp9OMRrUUbpI5lXqROjg\/MBWm262OkBlMdHIN6Lo75beJLN2p4RaSCX4s5vceSpARbitiCJY61R6YWYYZyU\/\/29iNchR1XxX3CpUdhPZYye5r5j89X1Kunmceir6rTFbVrhMAkW2lPM76gdyFY04pmV0toS+HwAkh7RyVGA\/ymbQkhJr\/HR1jtEtYCDKcYq0aP+pJ3onSv7EKMg32jf14jOsYqjo\/2TnI\/AxCrfW5CQvif5+mfcTJJ5Dxzh+5Kc1DhSYzdgR3SQ\/EQIDAQAB"
}
Create Payer/Method
POST
https://gateway.pawn-pay.com/api/v1/public/tokenize
This endpoint takes the encrypted payload to create the Payer and Payment Method records required to process the transaction.
The unencrypted payload has the same requirements as Create Payer and Create Method and supports Credit, Track and ACH Payment Methods.
The response contains the Payment Method record that was created along with the associated Payer ID.
Unencrypted payload requirements (credit)field | required | validation |
---|---|---|
payer.name | true | required, string, max:255 |
payer.email | false | nullable, string, email, max:255 |
payer.phone | false | phone, string, max:255 |
payer.address.street | false | string, max:50 |
payer.address.city | false | string, max:25 |
payer.address.state | false | alpha, min:2, max:3 |
payer.address.postal | false | alpha_num, min:5, max:10 |
payer.address.country | false | alpha, size:3 |
payment_method.name | false | string, max:255 |
payment_method.type | true | required, |
payment_method.sub_type | true | required, |
payment_method.account_name | true | required, string, max:255 |
payment_method.address.street | false | string, max:50 |
payment_method.address.city | false | string, max:25 |
payment_method.address.state | false | alpha, min:2, max:3 |
payment_method.address.postal | true | required, alpha_num, min:5, max:10 |
payment_method.address.country | false | alpha, size:3 |
payment_method.account | true | required, luhn |
payment_method.exp | true | required, date_format:my, after:last day of previous month |
payment_method.cvv | true | required, digits_between:3,4 |
field | required | validation |
---|---|---|
key_id | true | required, string |
payload | true | required, string |
{
"payer": {
"name": "Johnny Test",
"email": "j.test@example.com",
"phone": "+19544941234",
"address": {
"street": "1234 test street",
"city": "Townsville",
"state": "GA",
"postal": 30380,
"country": "USA"
}
},
"payment_method": {
"name": "Johnny's Visa",
"type": "credit",
"sub_type": "visa",
"account_name": "Johnny Test",
"account": "4242424242424242",
"exp": "0526",
"cvv": "123",
"address": {
"street": "1234 test street",
"city": "Townsville",
"state": "GA",
"postal": 30380,
"country": "USA"
}
}
}
Create Payer/Method request:
{
"key_id": "bv7fUdtTRSLP",
"payload": "158F6I0ad2IhE131n6AjCeL2h\/cgP5ySQLStxJ\/fLyfNo5n6kjiy+\/MxUvLWujorE82qvQT\/rckqy5kLvi0gdMPbyvWgCOAxq0DgU\/TrI7WuNjSIO4X1VKeQRhgNhvSvjsw3fd8F2Bv5Fv+mDvj+r99iVcF\/Fv9KhDcgBZqR58FkkHJ0FNTcM0m+pMaRzxc9F48GHENGIuGwyK++flECXojcFrZ9b92ifUOdO2gRxMmmJq7PNVZLMOX04DjenX31jhsL+7WuPxOhhjNUrxpzSewmLylNq3XuPlM8utHhbeo9Xw8z6NG9iQAcicrZNCMEftWjoFqfcU5URB6esB+SL2yiA==78IiuZCRkY9MXwH5fMxPXTlWbbblKaKAw3NDPB9b8Ug="
}
Create Payer/Method response:
{
"id": "05fa04e9-807b-4a2a-b503-31437e803e3d",
"name": "Johnny's Visa",
"type": "credit",
"sub_type": "visa",
"account_name": "Johnny Test",
"last_four": "4242",
"exp": "0526",
"address": {
"street": "1234 test street",
"city": "Townsville",
"state": "GA",
"postal": 30380,
"country": "USA"
},
"payer_id": "1bd2e600-acc6-443f-a796-8103458af1e7"
}
Transactions
Authorize Transaction
POST
https://gateway.pawn-pay.com/api/v1/merchant/transactions/authorize
Authorizing, Capturing and Processing Transactions can be done by providing the full information for a Payer and Method record or the one or both of the IDs of those models.
This allows you to simplify your requests and allows Payers to use existing Payment Methods without having to re-enter their details.
Examples of the different requests can be found below.
Note: Authorizing a Transaction lets you later change the Capture amount to an amount less than or equal to the Authorization amount. If you don't plan on Authorizing and Capturing for different amounts you should use Process.
Note: If you create and Authorization and do not Capture it you should Void it as the Payers bank will hold funds for your Authorization for a day or so before releasing them. Be kind to your customers 😊
Authorize Transaction with existing Payer and Payment Method
POST
https://gateway.pawn-pay.com/api/v1/merchant/transactions/authorize
Note: the Payment Method must belong to the Payer to be used in this way.
field | required | validation |
---|---|---|
amount | true | required, integer, min:1 |
currency | true | required, string, |
invoice | true | required |
invoice.number | true | required, string, max:126 |
invoice.description | true | required, string, max:255 |
invoice.items.*.name | true | required, string, max:126 |
invoice.items.*.description | false | nullable, string, max:255 |
invoice.items.*.quantity | true | required, integer, min:1 |
invoice.items.*.price | true | required, integer, min:0 |
invoice.sub_total | false | integer, min:1 |
invoice.discounts.*.name | true | required, string, max:126 |
invoice.discounts.*.description | false | nullable, string, max:255 |
invoice.discounts.*.quantity | true | required, integer, min:1 |
invoice.discounts.*.price | true | required, integer, min:1 |
invoice.total | true | required, integer, min:1, same:amount |
user_agent | false | sometimes, string |
ip_address | false | sometimes, ip |
payer | true | required |
payer.id | false | string, |
payment_method | true | required |
payment_method.id | false | string, |
{
"amount": 21600,
"currency": "USD",
"payer": {
"id": "1bd2e600-acc6-443f-a796-8103458af1e7"
},
"payment_method": {
"id": "05fa04e9-807b-4a2a-b503-31437e803e3d"
},
"invoice": {
"number": "I-4311",
"description": "Just a whole bunch of lemons, like loads of them.",
"items": [
{
"name": "The Lemons",
"description": "High quality citric goodness (lb)",
"quantity": 20,
"price": 1200
}
],
"discounts": [
{
"name": "Friends and Family",
"description": "My cousin loves these lemons",
"quantity": 1,
"price": 2400
}
],
"total": 21600
},
"user_agent": "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/79.0.3945.130 Safari\/537.36",
"ip_address": "253.254.133.107"
}
Authorize Transaction response:
{
"id": "705925a8-8535-4a78-a615-e3b956b1f4ec",
"amount": 21600,
"status": "authorized",
"captured_at": null,
"voided_at": null,
"refunded_at": null,
"settled_at": null,
"batch_id": null,
"description": "It's still about the lemons.",
"invoice": {
"number": "I-4311",
"description": "Just a whole bunch of lemons, like loads of them.",
"items": [
{
"name": "The Lemons",
"description": "High quality citric goodness (lb)",
"quantity": 20,
"price": 1200
}
],
"discounts": [
{
"name": "Friends and Family",
"description": "My cousin loves these lemons",
"quantity": 1,
"price": 2400
}
],
"total": 21600
},
"payer_id": "1bd2e600-acc6-443f-a796-8103458af1e7",
"method_id": "05fa04e9-807b-4a2a-b503-31437e803e3d",
"charge_rate": 0.0275,
"trans_fee": 15,
"cost": 594,
"net": 21006
}
Authorize Transaction with new Payer and/or Payment Method
POST
https://gateway.pawn-pay.com/api/v1/merchant/transactions/authorize
This request will attempt to find or create matching records for both the Payer and the Payment Method.
Payers are unique by email so the gateway will try to use the email field to find a matching Payer record before creating a new one.
Payment Methods are retrieved by type, sub type, last four digits and expiration. The gateway will try to find a matching Payment Method before creating a new one.
Note: the requirements for the payment_method
fields are the same as in the Create Method requests and change depending on the type provided. The example below is for the type 'credit', please review the request above for the method type you're using.
field | required | validation |
---|---|---|
amount | true | required, integer, min:1 |
currency | true | required, string, |
invoice | true | required |
invoice.number | true | required, string, max:126 |
invoice.description | true | required, string, max:255 |
invoice.items.*.name | true | required, string, max:126 |
invoice.items.*.description | false | nullable, string, max:255 |
invoice.items.*.quantity | true | required, integer, min:1 |
invoice.items.*.price | true | required, integer, min:0 |
invoice.sub_total | false | integer, min:1 |
invoice.discounts.*.name | true | required, string, max:126 |
invoice.discounts.*.description | false | nullable, string, max:255 |
invoice.discounts.*.quantity | true | required, integer, min:1 |
invoice.discounts.*.price | true | required, integer, min:1 |
invoice.total | true | required, integer, min:1, same:amount |
user_agent | false | sometimes, string |
ip_address | false | sometimes, ip |
payer | true | required |
payer.name | true | required, string, string, max:255 |
payer.email | false | nullable, string, email, max:255 |
payer.phone | false | phone, string, max:255 |
payer.address.street | false | string, max:255 |
payer.address.city | false | string, max:255 |
payer.address.state | false | alpha, min:2, max:3 |
payer.address.postal | false | alpha_num, min:5, max:6 |
payer.address.country | false | alpha, size:3 |
payment_method | true | required |
payment_method.name | false | string, max:255 |
payment_method.type | true | required, |
payment_method.sub_type | true | required, |
payment_method.account_name | true | required, string, max:255 |
payment_method.address.street | false | string, max:50 |
payment_method.address.city | false | string, max:25 |
payment_method.address.state | false | alpha, min:2, max:3 |
payment_method.address.postal | true | required, alpha_num, min:5, max:10 |
payment_method.address.country | false | alpha, size:3 |
payment_method.account | true | required, luhn |
payment_method.exp | true | required, date_format:my, after:last day of previous month |
payment_method.cvv | true | required, digits_between:3,4 |
{
"amount": 21600,
"currency": "USD",
"payer": {
"name": "Johnny Test",
"email": "j.test@example.com",
"phone": "+19544941234",
"address": {
"street": "1234 test street",
"city": "Townsville",
"state": "GA",
"postal": 30380,
"country": "USA"
}
},
"payment_method": {
"name": "Johnny's Visa",
"type": "credit",
"sub_type": "visa",
"account_name": "Johnny Test",
"account": "4242424242424242",
"exp": "0526",
"cvv": "123",
"address": {
"street": "1234 test street",
"city": "Townsville",
"state": "GA",
"postal": 30380,
"country": "USA"
}
},
"invoice": {
"number": "I-4311",
"description": "Just a whole bunch of lemons, like loads of them.",
"items": [
{
"name": "The Lemons",
"description": "High quality citric goodness (lb)",
"quantity": 20,
"price": 1200
}
],
"discounts": [
{
"name": "Friends and Family",
"description": "My cousin loves these lemons",
"quantity": 1,
"price": 2400
}
],
"total": 21600
},
"user_agent": "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/79.0.3945.130 Safari\/537.36",
"ip_address": "253.254.133.107"
}
Authorize Transaction response:
{
"id": "705925a8-8535-4a78-a615-e3b956b1f4ec",
"amount": 21600,
"status": "authorized",
"captured_at": null,
"voided_at": null,
"refunded_at": null,
"settled_at": null,
"batch_id": null,
"description": "It's still about the lemons.",
"invoice": {
"number": "I-4311",
"description": "Just a whole bunch of lemons, like loads of them.",
"items": [
{
"name": "The Lemons",
"description": "High quality citric goodness (lb)",
"quantity": 20,
"price": 1200
}
],
"discounts": [
{
"name": "Friends and Family",
"description": "My cousin loves these lemons",
"quantity": 1,
"price": 2400
}
],
"total": 21600
},
"payer_id": "1bd2e600-acc6-443f-a796-8103458af1e7",
"method_id": "05fa04e9-807b-4a2a-b503-31437e803e3d",
"charge_rate": 0.0275,
"trans_fee": 15,
"cost": 594,
"net": 21006
}
Capture a Transaction from an existing Authorization
POST
https://gateway.pawn-pay.com/api/v1/merchant/transactions/{trans_id}/capture
Leaving the amount field blank will attempt to capture the Transaction for the full amount of the Authorization.
The amount of the capture must be less than or equal to the existing Authorization.
If the currency code is not provided and will default to the currency code of the Authorization.
Note: Transactions may only be captured once.
field | required | validation |
---|---|---|
amount | false | integer, min:1 |
currency | false | alpha, size:3 |
{
"amount": 21600
}
Capture Transaction response:
{
"id": "705925a8-8535-4a78-a615-e3b956b1f4ec",
"amount": 21600,
"status": "captured",
"captured_at": "2019-09-13 19:42:46",
"voided_at": null,
"refunded_at": null,
"settled_at": null,
"batch_id": null,
"description": "It's still about the lemons.",
"invoice": {
"number": "I-4311",
"description": "Just a whole bunch of lemons, like loads of them.",
"items": [
{
"name": "The Lemons",
"description": "High quality citric goodness (lb)",
"quantity": 20,
"price": 1200
}
],
"discounts": [
{
"name": "Friends and Family",
"description": "My cousin loves these lemons",
"quantity": 1,
"price": 2400
}
],
"total": 21600
},
"payer_id": "1bd2e600-acc6-443f-a796-8103458af1e7",
"method_id": "05fa04e9-807b-4a2a-b503-31437e803e3d",
"charge_rate": 0.0275,
"trans_fee": 15,
"cost": 594,
"net": 21006
}
Process a Transaction
POST
https://gateway.pawn-pay.com/api/v1/merchant/transactions
Processing a Transaction is the same thing as Authorizing and then Capturing it, just skipping the intermediate step of attempting and Authorization first and having the option to capture for a different amount later.
Note: the request rules are the same as Authorizing a Transaction.
The following example uses an existing Payer and Method.
field | required | validation |
---|---|---|
amount | true | required, integer, min:1 |
currency | true | required, string, |
invoice | true | required |
invoice.number | true | required, string, max:126 |
invoice.description | true | required, string, max:255 |
invoice.items.*.name | true | required, string, max:126 |
invoice.items.*.description | false | nullable, string, max:255 |
invoice.items.*.quantity | true | required, integer, min:1 |
invoice.items.*.price | true | required, integer, min:0 |
invoice.sub_total | false | integer, min:1 |
invoice.discounts.*.name | true | required, string, max:126 |
invoice.discounts.*.description | false | nullable, string, max:255 |
invoice.discounts.*.quantity | true | required, integer, min:1 |
invoice.discounts.*.price | true | required, integer, min:1 |
invoice.total | true | required, integer, min:1, same:amount |
user_agent | false | sometimes, string |
ip_address | false | sometimes, ip |
payer | true | required |
payer.id | false | string, |
payment_method | true | required |
payment_method.id | false | string, |
{
"amount": 21600,
"currency": "USD",
"payer": {
"id": "1bd2e600-acc6-443f-a796-8103458af1e7"
},
"payment_method": {
"id": "05fa04e9-807b-4a2a-b503-31437e803e3d"
},
"invoice": {
"number": "I-4311",
"description": "Just a whole bunch of lemons, like loads of them.",
"items": [
{
"name": "The Lemons",
"description": "High quality citric goodness (lb)",
"quantity": 20,
"price": 1200
}
],
"discounts": [
{
"name": "Friends and Family",
"description": "My cousin loves these lemons",
"quantity": 1,
"price": 2400
}
],
"total": 21600
},
"user_agent": "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/79.0.3945.130 Safari\/537.36",
"ip_address": "253.254.133.107"
}
Process Transaction response:
{
"id": "705925a8-8535-4a78-a615-e3b956b1f4ec",
"amount": 21600,
"status": "captured",
"captured_at": "2019-09-13 19:42:46",
"voided_at": null,
"refunded_at": null,
"settled_at": null,
"batch_id": null,
"description": "It's still about the lemons.",
"invoice": {
"number": "I-4311",
"description": "Just a whole bunch of lemons, like loads of them.",
"items": [
{
"name": "The Lemons",
"description": "High quality citric goodness (lb)",
"quantity": 20,
"price": 1200
}
],
"discounts": [
{
"name": "Friends and Family",
"description": "My cousin loves these lemons",
"quantity": 1,
"price": 2400
}
],
"total": 21600
},
"payer_id": "1bd2e600-acc6-443f-a796-8103458af1e7",
"method_id": "05fa04e9-807b-4a2a-b503-31437e803e3d",
"charge_rate": 0.0275,
"trans_fee": 15,
"cost": 594,
"net": 21006
}
Reverse Transaction
POST
https://gateway.pawn-pay.com/api/v1/merchant/transactions/{trans_id}/reverse
This request will attempt to void/refund an existing Transaction, depending on it's current state and the amount being refunded.
Transactions that have not yet been captured will be voided. Transactions cannot be partially voided.
Transactions that have been captured will be refunded for the full amount of the Transaction if an amount is not provided.
You may refund Transactions multiple times up to the total amount of the original Transaction.
Transaction in the voided
state cannot be voided or refunded.
field | required | validation |
---|---|---|
amount | false | nullable, integer, min:1 |
currency | false | required_with:amount, |
{
"amount": 21600
}
Reverse Transaction response:
{
"id": "705925a8-8535-4a78-a615-e3b956b1f4ec",
"amount": 21600,
"amount_refunded": 21600,
"status": "refunded",
"captured_at": "2019-09-13 19:42:46",
"voided_at": null,
"refunded_at": "2019-09-13 19:50:12",
"settled_at": null,
"batch_id": null,
"description": "It's still about the lemons.",
"invoice": {
"number": "I-4311",
"description": "Just a whole bunch of lemons, like loads of them.",
"items": [
{
"name": "The Lemons",
"description": "High quality citric goodness (lb)",
"quantity": 20,
"price": 1200
}
],
"discounts": [
{
"name": "Friends and Family",
"description": "My cousin loves these lemons",
"quantity": 1,
"price": 2400
}
],
"total": 21600
},
"payer_id": "1bd2e600-acc6-443f-a796-8103458af1e7",
"method_id": "05fa04e9-807b-4a2a-b503-31437e803e3d",
"charge_rate": 0.0275,
"trans_fee": 15,
"cost": 594,
"net": -594
}
Get Transaction
GET
https://gateway.pawn-pay.com/api/v1/merchant/transactions/{trans_id}
{
"id": "705925a8-8535-4a78-a615-e3b956b1f4ec",
"amount": 21600,
"status": "captured",
"captured_at": "2019-09-13 19:42:46",
"voided_at": null,
"refunded_at": null,
"settled_at": null,
"batch_id": null,
"description": "It's still about the lemons.",
"invoice": {
"number": "I-4311",
"description": "Just a whole bunch of lemons, like loads of them.",
"items": [
{
"name": "The Lemons",
"description": "High quality citric goodness (lb)",
"quantity": 20,
"price": 1200
}
],
"discounts": [
{
"name": "Friends and Family",
"description": "My cousin loves these lemons",
"quantity": 1,
"price": 2400
}
],
"total": 21600
},
"payer_id": "1bd2e600-acc6-443f-a796-8103458af1e7",
"method_id": "05fa04e9-807b-4a2a-b503-31437e803e3d",
"charge_rate": 0.0275,
"trans_fee": 15,
"cost": 594,
"net": 21006
}
Get Batch
GET
https://gateway.pawn-pay.com/api/v1/merchant/batches/{batch_id}
This endpoint lists all the transaction included in the requested batch as well as a summary of the total settled transactions in the batch.
{
"id": 123456,
"date": "2019-01-02 08:45:35",
"amount": 58513,
"transactions": [
{
"id": "705925a8-8535-4a78-a615-e3b956b1f4ec",
"amount": 21600,
"status": "captured",
"captured_at": "2019-09-13 19:42:46",
"voided_at": null,
"refunded_at": null,
"settled_at": "2019-09-14 09:15:52",
"batch_id": 12345,
"description": "It's still about the lemons.",
"invoice": {
"number": "I-4311",
"description": "Just a whole bunch of lemons, like loads of them.",
"items": [
{
"name": "The Lemons",
"description": "High quality citric goodness (lb)",
"quantity": 20,
"price": 1200
}
],
"discounts": [
{
"name": "Friends and Family",
"description": "My cousin loves these lemons",
"quantity": 1,
"price": 2400
}
],
"total": 21600
},
"payer_id": "1bd2e600-acc6-443f-a796-8103458af1e7",
"method_id": "05fa04e9-807b-4a2a-b503-31437e803e3d"
}
]
}
Webhooks
What's a webhook?
Webhooks allow the gateway to communicate with your systems directly when events happen in real time, rather than waiting and polling for those changes later.
Ok cool, what do I need to do?
First there needs to a publicly accessible URL secured with TLS for the gateway to send webhooks to. The URL can include query parameters.
The gateway will send requests to the URL as they're triggered by various events by using the HTTP verb POST.
No special headers should be required for the URL to accept the request.
The URL should return an empty response with the HTTP status code 200 for any request it receives.
Each Merchant can have it's own endpoints for each webhook type, this can be configured via the Merchant API.
Ok, awesome. So what're you sending me?
The webhook request body will always be a JSON encoded object.
The content of the webhook will change depending on the type of event that's being sent, but the basic structure is always the same. Check out the example webhook requests provided here.
That sounds great, but how will I know the message is from you?
Astute question, you're obviously very security minded. Us too.
The webhook request will have token, signature and timestamp
headers which you should use to validate the request is from us.
The signature
is a encrypted string which is the token
followed by the timestamp
of the request encrypted with SHA256 by the merchants webhook_secret
. So only parties that know the webhook secret can decrypt and validate the signature.
You should do your own calculation of what the signature should be using the webhook secret and compare that to the one in the request header. If they match the request is from us, if not you should ignore the request and just send the 200 response, the baddies will never know the difference.
<?php
/**
* Example code:
* Implement this however you want but this is the
* basic shape of how to validate the request signature.
*/
http_response_code(200);
// Keep this secret and out of your repository
const WEBHOOK_SECRET = 'ABC123';
// Incoming request header values
$token = $_SERVER['HTTP_TOKEN'];
$timestamp = $_SERVER['HTTP_TIMESTAMP'];
$request_signature = $_SERVER['HTTP_SIGNATURE'];
$computed_signature = hash_hmac('sha256', $token.$timestamp, WEBHOOK_SECRET);
// Compare signatures
if (!hash_equals($computed_signature, $request_signature)) {
// The signatures don't match
return null;
}
/*
* Signatures match, handle the request
* @todo awesome code goes here
* Lookup merchants by id
* Switches based on 'type'
*/
return null;
Create a Webhook
POST
https://gateway.pawn-pay.com/api/v1/merchant/webhooks
A webhook will be posted to the url you provide when the event is triggered.
The URL may include parameters.
Note: should go without saying but don't include any secret information in the url.
field | required | validation |
---|---|---|
event_type | true | required, |
url | true | required, string, url, https |
{
"event_type": "payer.created",
"url": "https://www.example.com/webhooks?param=true&test=1134"
}
Create Webhook response:
{
"id": "01dd90f1-0e16-4653-9857-dab4b794c758",
"event_type": "payer.created",
"url": "https://www.example.com/webhooks?param=true&test=1134",
"last_hook": null
}
Show a Webhook
GET
https://gateway.pawn-pay.com/api/v1/merchant/webhooks/{webhook_id}
Note: created_at is UTC time.
{
"id": "01dd90f1-0e16-4653-9857-dab4b794c758",
"event_type": "payer.created",
"url": "https://www.example.com/webhooks?param=true&test=1134",
"last_hook": {
"status": 200,
"payload": {
"type": "payer.created",
"merchant_id": "testing",
"data": {
"id": "01dd90f1-0e16-4653-9857-dab4b794c758",
"name": "Raheem Shanahan",
"email": "tbalistreri@example.com",
"phone": "1-713-417-3795",
"address": {
"city": "Dangeloburgh",
"state": "NV",
"postal": "27781",
"street": "2842 Carmela Park",
"country": "GUM"
}
}
},
"created_at": "2019-09-13 19:42:46"
}
}
Update a Webhook
PATCH
https://gateway.pawn-pay.com/api/v1/merchant/webhooks/{webhook_id}
field | required | validation |
---|---|---|
event_type | true | required, |
url | true | required, string, url, https |
{
"url": "https://www.example.com/webhooks/v2?param=true&test=1134"
}
Update Webhook response:
{
"id": "01dd90f1-0e16-4653-9857-dab4b794c758",
"event_type": "payer.created",
"url": "https://www.example.com/webhooks?param=true&test=1134",
"last_hook": {
"status": 200,
"payload": {
"type": "payer.created",
"merchant_id": "testing",
"data": {
"id": "01dd90f1-0e16-4653-9857-dab4b794c758",
"name": "Raheem Shanahan",
"email": "tbalistreri@example.com",
"phone": "1-713-417-3795",
"address": {
"city": "Dangeloburgh",
"state": "NV",
"postal": "27781",
"street": "2842 Carmela Park",
"country": "GUM"
}
}
},
"created_at": "2019-09-13 19:42:46"
}
}
Delete a Webhook
DELETE
https://gateway.pawn-pay.com/api/v1/merchant/webhooks/{webhook_id}
{
"message": "deleted webhook 01dd90f1-0e16-4653-9857-dab4b794c758"
}
List Webhooks
GET
https://gateway.pawn-pay.com/api/v1/merchant/webhooks/events/{event_type}
[
{
"id": "01dd90f1-0e16-4653-9857-dab4b794c758",
"event_type": "payer.created",
"url": "https://www.example.com/webhooks?param=true&test=1134",
"last_hook": {
"status": 200,
"payload": {
"type": "payer.created",
"merchant_id": "testing",
"data": {
"id": "01dd90f1-0e16-4653-9857-dab4b794c758",
"name": "Raheem Shanahan",
"email": "tbalistreri@example.com",
"phone": "1-713-417-3795",
"address": {
"city": "Dangeloburgh",
"state": "NV",
"postal": "27781",
"street": "2842 Carmela Park",
"country": "GUM"
}
}
},
"created_at": "2019-09-13 19:42:46"
}
}
]
Webhook Types
Payer Created
This webhook is sent when a Payer record is created.
{
"type": "payer.created",
"merchant_id": "testing",
"data": {
"id": "01dd90f1-0e16-4653-9857-dab4b794c758",
"name": "Raheem Shanahan",
"email": "tbalistreri@example.com",
"phone": "1-713-417-3795",
"address": {
"city": "Dangeloburgh",
"state": "NV",
"postal": "27781",
"street": "2842 Carmela Park",
"country": "GUM"
}
}
}
Payer Updated
This webhook is sent when a Payer record is updated.
{
"type": "payer.updated",
"merchant_id": "testing",
"data": {
"id": "01dd90f1-0e16-4653-9857-dab4b794c758",
"name": "Raheem Shanahan",
"email": "tbalistreri@example.com",
"phone": "1-713-417-3795",
"address": {
"city": "Dangeloburgh",
"state": "NV",
"postal": "27781",
"street": "2842 Carmela Park",
"country": "GUM"
}
}
}
Payer Deleted
This webhook is sent when a Payer record is deleted.
{
"type": "payer.deleted",
"merchant_id": "testing",
"data": {
"id": "01dd90f1-0e16-4653-9857-dab4b794c758",
"name": "Raheem Shanahan",
"email": "tbalistreri@example.com",
"phone": "1-713-417-3795",
"address": {
"city": "Dangeloburgh",
"state": "NV",
"postal": "27781",
"street": "2842 Carmela Park",
"country": "GUM"
}
}
}
Transaction Created
This webhook is sent when a Transaction record is created.
{
"type": "transaction.created",
"merchant_id": "testing",
"data": {
"id": "000c64d3-2eef-4bbe-8cc0-80a8d4c2f5c6",
"amount": 94595,
"invoice": {
"items": [
{
"name": "recusandae",
"price": 15170,
"quantity": 1,
"description": null
},
{
"name": "nam",
"price": 15885,
"quantity": 5,
"description": null
}
],
"total": 94595,
"number": "9ep-5357338",
"discounts": [],
"sub_total": 94595,
"description": null
},
"captured": false,
"refunded": false,
"voided": false,
"method_id": "36f85e57-5d1b-47ad-af6a-f9cd9b893c3e",
"payer_id": "494c5154-253e-4554-aac1-ce25e77d6847",
"status": "authorized",
"charge_rate": 0.0275,
"trans_fee": 15,
"cost": 2616,
"net": 91979
}
}
Transaction Refunded
This webhook is sent when a Transaction record is refunded.
{
"type": "transaction.refunded",
"merchant_id": "testing",
"data": {
"id": "000c64d3-2eef-4bbe-8cc0-80a8d4c2f5c6",
"amount": 94595,
"amount_refunded": 94595,
"invoice": {
"items": [
{
"name": "recusandae",
"price": 15170,
"quantity": 1,
"description": null
},
{
"name": "nam",
"price": 15885,
"quantity": 5,
"description": null
}
],
"total": 94595,
"number": "9ep-5357338",
"discounts": [],
"sub_total": 94595,
"description": null
},
"captured": true,
"refunded": true,
"voided": false,
"method_id": "36f85e57-5d1b-47ad-af6a-f9cd9b893c3e",
"payer_id": "494c5154-253e-4554-aac1-ce25e77d6847",
"status": "refunded",
"charge_rate": 0.0275,
"trans_fee": 15,
"cost": 2601,
"net": -2601
}
}
Transaction Voided
This webhook is sent when a Transaction record is voided.
{
"type": "transaction.voided",
"merchant_id": "testing",
"data": {
"id": "000c64d3-2eef-4bbe-8cc0-80a8d4c2f5c6",
"amount": 94595,
"invoice": {
"items": [
{
"name": "recusandae",
"price": 15170,
"quantity": 1,
"description": null
},
{
"name": "nam",
"price": 15885,
"quantity": 5,
"description": null
}
],
"total": 94595,
"number": "9ep-5357338",
"discounts": [],
"sub_total": 94595,
"description": null
},
"captured": false,
"refunded": false,
"voided": true,
"method_id": "36f85e57-5d1b-47ad-af6a-f9cd9b893c3e",
"payer_id": "494c5154-253e-4554-aac1-ce25e77d6847",
"status": "voided"
}
}
Transaction Retrieval
This webhook is sent when a retrieval request is issued against a Transaction.
These retrievals are inquiries by banks asking for more information about a Transaction, usually at the behest of a customer.
This often precedes a chargeback.
{
"type": "transaction.retrieval",
"merchant_id": "testing",
"data": {
"message": "Credit issued but not posted, or credit not issued",
"transaction_id": "000c64d3-2eef-4bbe-8cc0-80a8d4c2f5c6"
}
}
Transaction Chargeback
This webhook is sent when a chargeback is issued against a Transaction.
{
"type": "transaction.chargeback",
"merchant_id": "testing",
"data": {
"id": "5842986c-38e2-4a1d-92be-d23ab3ad9f6c",
"message": "Credit issued but not posted, or credit not issued",
"transaction_id": "000c64d3-2eef-4bbe-8cc0-80a8d4c2f5c6"
}
}
Transaction ACH Reject
This webhook is sent when a ACH Transaction is rejected.
This can be due to: a lack of funds in the payer ACH account, the ACH is closed, etc.
{
"type": "transaction.ach_reject",
"merchant_id": "testing",
"data": {
"message": "Credit issued but not posted, or credit not issued",
"transaction_id": "000c64d3-2eef-4bbe-8cc0-80a8d4c2f5c6"
}
}
Batch Created
This webhook is sent when a new Batch is created.
{
"type": "batch.created",
"merchant_id": "testing",
"data": {
"id": "123456",
"date": "2019-01-02 08:45:35",
"transaction_count": 15,
"amount": 58513
}
}
Change Log
We do our absolute best to make sure any changes made only add functionality and do no effect existing integrations. If there is a change that effects existing integrations we will notify our partners of the change in advance of the change.
15-01-2021:
- Payer records no longer require an email.
15-05-2020:
-
The Reverse Transaction endpoints were documented as
/refund
instead of the correct url/reverse
05-02-2020:
- Added Client Side Tokenization endpoints. These let public clients use a Merchants public key to request card tokens for use with transactions.
04-02-2020:
-
Added
user_agent
andip_address
fields to transaction requests
Effected endpoints Authorize Transaction and Process Transaction
You are recommended to pass the user agent and IP address of the client that requests the transaction for each transaction.
30-01-2020:
- Clarified the return codes on Create Payer
11-14-2019:
-
Added transaction attributes charge_rate, trans_fee, cost and net.
- cost is
(amount * charge_rate) + trans_fee
- net is
amount - amount_refunded - cost
- cost is
-
Added webhooks for partners, they work the same, just get sent to partners for all of their managed merchants
For instance if you add a
transaction.created
webhook that gets sent, you'll receive a webhook anytime any of your merchants creates a transaction. Partners have their own webhook secrets.
11-12-2019: Added endpoints/webhook for Batches, added references to batch_id to transaction objects. Added reference to PHP SDK.
11-03-2019: Updated documentation address requirements, they're a little smaller now
11-02-2019: Updated documentation for Refund requests
11-01-2019: Updated documentation for Merchant requests, added successful response example
10-25-2019: Updated documentation for Transaction requests (authorize, process). No change to behavior, just documentation.
10-24-2019: Added information about Rate Limits, these were always in place but not documented.
09-15-2019: 🎂 happy birthday.