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 as YYYY-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 wait
X-RateLimit-Reset: 1571946053 unix timestamp when limit resets
Note: 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 header:
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.

Failed validation response:
{
    "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.

Successful response:
{
    "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]+$/
email 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,
Create Merchant request:
{
    "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.

Successful response:
{
    "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
email 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
Create Payer request:
{
    "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
email 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
Update Payer request:
{
    "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.

Delete Payer response:
{
    "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
Create credit Method request:
{
    "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
Create ACH Method request:
{
    "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
Create track Method request:
{
    "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.

Payment Methods 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"
    },
    {
        "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
Update Payment Method request:
{
    "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

Delete Payment Method response:
{
    "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.

Javascript encryption example code:
// 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
Get Public Key request:
{
    "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
Payload requirements
field required validation
key_id true required, string
payload true required, string
Create Payer/Method 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": "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,
Authorize Transaction request:
{
    "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
Authorize Transaction request:
{
    "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
Capture Transaction request:
{
    "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,
Process Transaction request:
{
    "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,
Reverse Transaction request:
{
    "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}

Get 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
}

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.

Get Batch response:
{
    "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.

Example Webhook handler (PHP):
<?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
Create Webhook request:
{
    "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.

Show 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"
    }
}
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
Update Webhook request:
{
    "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}

Delete Webhook response:
{
    "message": "deleted webhook 01dd90f1-0e16-4653-9857-dab4b794c758"
}
List Webhooks

GET https://gateway.pawn-pay.com/api/v1/merchant/webhooks/events/{event_type}

List Webhooks 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"
        }
    }
]

Webhook Types

Payer Created

This webhook is sent when a Payer record is created.

Payer Created Webhook:
{
    "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.

Payer Updated Webhook:
{
    "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.

Payer Deleted Webhook:
{
    "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.

Transaction Created Webhook:
{
    "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.

Transaction Refunded Webhook:
{
    "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.

Transaction Voided Webhook:
{
    "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.

Retrieval Webhook:
{
    "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.

Chargeback Webhook:
{
    "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.

ACH Reject Webhook:
{
    "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.

Batch Created Webhook:
{
    "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 and ip_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:

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
  • 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.