Introduction

Welcome to the PayWhirl API Reference. Here you will find details about our API and the methods it supports. We are consistently trying to make our web services better, so if you run into any issues or need help getting set up, please reach out to our support team.

Here's a high-level overview of how the PayWhirl services work:

  1. Make a PayWhirl account
  2. Create some plans
  3. Add your customers
  4. Add the customer's payment cards
  5. Attach plans to customers using subscribe
  6. Sit back and let us handle the recurring payments as you focus on your core product

The details for the rest of the options you see to the left will be explained in their individual sections.

Next will be a slightly more technical explanation of the usage details. The target audience is assumed to have general knowledge of software development and that our RESTful API uses POST and GET methods to send and receive data behind the scenes, so an active internet connection is required.


                

Authentication

In order to access the API, you will need an API key and secret. You can obtain one in the API key section of the main site. You can also find this page in your dashboard in the following section:

Settings & Account > Developer > API Keys

Note: please make sure to store this token securely, and never expose it in your front-end code, because it gives full access to your subscriptions to anyone who gets ahold of it. If you suspect your API key or secret may have been leaked, please revoke it it as soon as possible and create a new one.

After obtaining your token, you can call the /test endpoint to verify that your credentials are working. All calls made to the API will use the following endpoint: https://api.paywhirl.com.

Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/test');
Sample response:
var_export(json_decode($response->getBody())); array ( 'message' => 'Connection successful', )

PayWhirl libraries (deprecated)

Note: since every mainstream web framework includes high quality tools for accessing APIs and processing webhooks, we decided to deprecate our dedicated libraries. This leaves you with the maximum flexibility in choosing your preferred tech stack. The following section is left for reference only.

The PayWhirl libraries follow a typical object-oriented or object-based software pattern, where you simply instantiate a PayWhirl object using your API credentials and call a variety of methods to manipulate your account while the PayWhirl object abstracts away network authentication and requests.

The vast majority of methods recieve a JSON object from our servers that is translated by the library into that language's native dictionary-like data structure. The returned object is either something that you've directly requested, or is an object that can be seen as a sort of receipt, verifying that your changes have been acknowledged and stored. Anything that doesn't operate this way will explicitly say so in its section of this documentation.

Place the library file in your project and import the class so that you can instantiate a PayWhirl object. When you create a new PayWhirl object you need to pass in your API key and secret, which can be found in the API key section of the main site.

Rate Limits

PayWhirl enforces API rate limiting for any given interval. This means you cannot exceed the allowed number of requests to the API in a predefined interval. Current values are:

Number of requests: 360
Interval: 1 minute(s)

If you excceed the number of allowed requests for the given interval, HTTP response code of 429 will be returned. To help mitigate this problem, 3 HTTP response headers are available for you to check with each request you make:

  1. x-ratelimit-limit: the max number of requests you can make in a given interval
  2. x-ratelimit-remaining: how many of those are remaining for you to make until the interval refreshes
  3. x-ratelimit-reset: timestamp of the next moment when the remaining limits will be reset to the allowed one (e.g. 1547130118)

Customers

Customer objects are the primary abstraction mechanism for storing information about a specific real person. You can later attach customers to plans using a subscription, or you can charge them directly for single-instance payments.

The customer section of the API allows you to create, update, and retrieve information about your customers.

You might find it useful to keep track of your customer id numbers as you create them so that you have an easy way to select customers you want to find information about or to manipulate.

GET /customers

The primary purpose of this method is to retrieve a selection of your customers (that can be ordered via the parameters below). This is useful for keeping track of your customers in a local database for bookkeeping purposes and for having a local copy that you can edit before submitting changes via the update customer method.

An important distinction to keep in mind is that the customer's id is distinct from your (the PayWhirl account holder's) user_id.

This method returns an indexed array containing Customers of type object.

Parameters

For parameters, the method will accept one of the key-value pairs listed below.

limit
integer Number of entries to return. Limit 100.
order_key
string Field to sort by. Default `id`.
order_direction
string Direction to order. (asc/desc) Defaults to `desc`.
before_id
integer All customers returned will have an ID less than before_id.
after_id
integer All customers returned will have an ID greater than after_id
keyword
string (Optional) Filter by keyword
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/customers/?limit=10&order_key=id&order_direction=asc');
Sample response:
var_export(json_decode($response->getBody())); array ( 0 => array ( 'id' => 54398, 'user_id' => 1234, 'first_name' => 'Awesome', 'last_name' => 'Person', 'email' => 'awesome+person@paywhirl.com', 'phone' => '4564564566', 'address' => '123, Easy Street', 'city' => 'Los Angeles', 'state' => 'California', 'zip' => '93003', 'country' => 'US', 'gateway_reference' => '783789y6r890', 'default_card' => 34, 'gateway_type' => 'PaywhirlTest', 'gateway_id' => 18, 'currency' => 'USD', 'utm_source' => '', 'utm_medium' => '', 'utm_term' => '', 'utm_content' => '', 'utm_campaign' => '', 'utm_group' => '', 'metadata' => '', 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-06T11:24:00.000000Z', 'deleted_at' => NULL, ), 1 => array ( 'id' => 54403, 'user_id' => 1234, 'first_name' => 'Joe', 'last_name' => 'Cool', 'email' => 'joe@paywhirl.com', 'phone' => '8978768768', 'address' => '123 Easy Street', 'city' => 'Dallas', 'state' => 'Texas', 'zip' => '93003', 'country' => 'US', 'gateway_reference' => 'cus_56549881e6e03', 'default_card' => 45, 'gateway_type' => 'Stripe', 'gateway_id' => 20, 'currency' => 'USD', 'utm_source' => '', 'utm_medium' => '', 'utm_term' => '', 'utm_content' => '', 'utm_campaign' => '', 'utm_group' => '', 'metadata' => '', 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-05T11:24:00.000000Z', 'deleted_at' => NULL, ), )

GET /customer/{id}

The primary purpose of this method is to retrieve information about an individual customer from the PayWhirl database for your personal use. This will most likely be useful to you as a tool for displaying customer information in your app, or as a way to grab a current version of a specific customer's data in order to modify it before submitting an update customer request.

This method returns a Customer object of type object.

Parameters

For parameters, you can select the customer you desire by passing in the customer's database assigned (integer) ["id"] or the user-supplied (string) ["email"].

Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/customer/54398');
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => 54398, 'user_id' => 1234, 'first_name' => 'Awesome', 'last_name' => 'Person', 'email' => 'awesome+person1@example.com', 'phone' => '4564564566', 'address' => '123, Easy Street', 'city' => 'Los Angeles', 'state' => 'California', 'zip' => '93003', 'country' => 'US', 'gateway_reference' => '783789y6r890', 'default_card' => 34, 'gateway_type' => 'PaywhirlTest', 'gateway_id' => 18, 'currency' => 'USD', 'utm_source' => '', 'utm_medium' => '', 'utm_term' => '', 'utm_content' => '', 'utm_campaign' => '', 'utm_group' => '', 'metadata' => '', 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-06T11:24:00.000000Z', 'deleted_at' => NULL, )

POST /auth/customer

Authenticate a Customer by passing in an email address and password.

Parameters

email
string Email address (required)
password
string Password (required)
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/auth/customer', [ 'json' => [ 'email' => 'awesome+person@paywhirl.com', 'password' => 'secret', ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'status' => 'success', )

POST /create/customer

Create a new Customer by passing in an array of key-value pairs. The parameters below tagged "(required)" can be used to create a minimal Customer. The ["email"] field in particular is used as one of two candidate keys for the database. The second candidate key is a database-supplied ["id"] field.

This method is the third step after setting up your account and creating some plans.

Later, when you're binding a customer and plan via a subscription, you will need to supply the customer's id.

For security reasons, we strongly advise leaving the password field blank, and letting the customer assign the password themselves, using a secure link in the Welcome email.

If you decide to provide your password anyway, note that the minimum viable one needs to be between 6 and 20 characters, and uses the following perl-compatible regex that might be useful to include in your own code:

/^[A-Za-z0-9~!@#$%^&*()_\-+=<>,.?\/;:{}\\\\[\]|]{6,}$/

Although the technical minimum password length is 6 characters, we do recommend that you stress the importance of a password that is around 14-ish characters long to harness the power of combinatoric explosions. It's also a good idea to remind people that they shouldn't use the same password for multiple websites or services.

It is also important to never locally store the password string, especially in plaintext. The plaintext password should be secured by the HTTPS request, and is hashed on our servers before it ever touches the database.

Email Settings: this method will trigger the Welcome Email event if you've elected to create and use a welcome template email.

Parameters

first_name
string First name (required)
last_name
string Last name (required)
email
string Email address (required)
currency
string Currency code of this user"s currency (ex. USD, GBP, AUD, CAD, EUR, etc) (required)
password
string Password
phone
integer Phone number
address
string Street address
city
string City
state
string State / Region
zip
string Zip / Postal Code
country
string Country Code (ex. US, GB, CA, AU, etc)
gateway_id
string PayWhirl ID of payment gateway to attach to customer. If left blank, first loaded gateway will be used.
utm_source
string Acquisition source (ex. google, bing, email, fall_campaign, etc)
utm_medium
string Acquisition medium (ex. CPC, banner, text_ad, etc)
utm_term
string Acquisition keyword
utm_content
string Acquisition Ad Content
utm_campaign
string Acquisition Ad Campaign
utm_group
string Acquisition Ad Group
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/create/customer', [ 'json' => [ 'first_name' => 'Sleve', 'last_name' => 'McDichael', 'email' => 'slevem@dandleton.com', 'currency' => 'USD', ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => '1234546', 'user_id' => 1234, 'first_name' => 'Sleve', 'last_name' => 'McDichael', 'email' => 'slevem@dandleton.com', 'phone' => '', 'address' => '', 'city' => '', 'state' => '', 'zip' => '', 'country' => '', 'gateway_reference' => '66b3418b2e496', 'default_card' => 0, 'gateway_type' => 'PaywhirlTest', 'gateway_id' => 18, 'currency' => 'USD', 'utm_source' => '', 'utm_medium' => '', 'utm_term' => '', 'utm_content' => '', 'utm_campaign' => '', 'utm_group' => '', 'metadata' => '', 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', 'deleted_at' => NULL, )

POST /update/customer

The update customer method requires that you return ALL of the customer information to be updated, so the standard workflow should be get customer, then change any data you want, then return all of the customer data. Any empty fields will be removed from the customer object.

The update method is mostly useful for things like changing the default card on file for a customer, or for fixing an error in a name. You cannot use this method unless you've already used the create customer method to register a customer object with our service.

The update method will read in an array of key-value pairs that you wish to alter, and will update the customer associated with the ["id"] contained in the collection passed in. If you don't know the ["id"] of the customer you wish to modify, you can utilize the /GET/customer/ method for your language and search by email address. If you don't know the email address, you can use the /GET/customers/ method to retrieve an array of all customers and their ids.

Parameters

id
string PayWhirl customer ID (required)
first_name
string First name
last_name
string Last name
email
string Email address
password
string Password
phone
integer Phone number
address
string Street address
city
string City
state
string State / Region
zip
string Zip / Postal Code
country
string Country Code (ex. US, GB, CA, AU, etc)
default_card
integer Default Payment Card ID
utm_source
string Acquisition source (ex. google, bing, email, fall_campaign, etc)
utm_medium
string Acquisition medium (ex. CPC, banner, text_ad, etc)
utm_term
string Acquisition keyword
utm_content
string Acquisition Ad Content
utm_campaign
string Acquisition Ad Campaign
utm_group
string Acquisition Ad Group
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/update/customer', [ 'json' => [ 'id' => 54398, 'first_name' => 'Stove', 'last_name' => 'Dandleton', 'email' => 'slevem@dandleton.com', 'currency' => 'USD', ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => 54398, 'user_id' => 1234, 'first_name' => 'Stove', 'last_name' => 'Dandleton', 'email' => 'slevem@dandleton.com', 'phone' => '4564564566', 'address' => '123, Easy Street', 'city' => 'Los Angeles', 'state' => 'California', 'zip' => '93003', 'country' => 'US', 'gateway_reference' => '783789y6r890', 'default_card' => 34, 'gateway_type' => 'PaywhirlTest', 'gateway_id' => 18, 'currency' => 'USD', 'utm_source' => '', 'utm_medium' => '', 'utm_term' => '', 'utm_content' => '', 'utm_campaign' => '', 'utm_group' => '', 'metadata' => '', 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', 'deleted_at' => NULL, )

POST /delete/customer

Delete a customer by its ID.

Parameters

id
integer Customer ID
forget
boolean Send "true" to forget and obfuscate customer data rather than soft-delete the customer. Defaults to false.
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/delete/customer', [ 'json' => [ 'id' => 54398, 'forget' => true, ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'status' => 'success', )

GET /questions

This method returns a list of all questions associated with your account that can be displayed on your checkout or sign up forms. For more information about the data fields, there is a graphical implementation in the PayWhirl dashboard that clearly elucidates the options.

The general purpose of any form question is for you to gather information from your customers without needing to use a secondary polling service. By utilizing the checkout questions, you can get small snippets of information that might be useful for your company to analyze. This method will most likely be useful for finding values needed to store in your database for easier parsing later.

Note that this does not grab questions associated with individual subscribers, but retrieves all of the questions associated with your PayWhirl account.

Parameters

limit
integer Number of records to return
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/questions?limit=10');
Sample response:
var_export(json_decode($response->getBody())); array ( 0 => array ( 'type' => 'select', 'name' => 'select-1454375030381', 'label' => 'Ground or Whole Bean?', 'className' => 'form-control', 'subtype' => NULL, 'toggle' => NULL, 'value' => NULL, 'description' => NULL, 'required' => true, 'values' => array ( 0 => array ( 'label' => 'Whole Bean', 'value' => 'Whole Bean', 'selected' => true, ), 1 => array ( 'label' => 'Ground', 'value' => 'Ground', ), ), 'rows' => NULL, 'placeholder' => '', 'multiple' => NULL, 'other' => NULL, 'min' => NULL, 'max' => NULL, 'step' => NULL, ), )

POST /update/answer

This method allows you to create or modify an answer provided by a given user, selected by ["customer_id"], ["question_name"], and ["answer"].

The general idea is that any given customer will answer questions during checkout (or with this method, whenever you choose). This method provides a way for you to associate a given answer with a customer and a question name. Later, you can use the get answers method to retrieve and parse the data you've acquired from your customers.

Parameters

question_name
string Question Name (required)
customer_id
integer PayWhirl customer ID (required)
answer
string Value of answer (required)
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/update/answer', [ 'json' => [ 'question_name' => 'select-1454375030381', 'customer_id' => 54398, 'answer' => 'Ground', ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 0 => array ( 'name' => 'select-1454375030381', 'label' => 'Ground or Whole Bean?', 'answer' => 'Ground', ), )

GET /answers

Get a complete lists of a customer's answers to profile questions by passing in an integer representing the customer ID.

The general idea of the get answers method is that it allows you to retrieve all of the answered questions associated with a given customer. The most common use-case for this is to download all of the information stored about your customers and store it in a local database, or convert it to a spreadsheet-readable format so that you can parse the feedback you've acquired.

Parameters

customer_id
integer Customer ID (required)
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/answers?customer_id=54398');
Sample response:
var_export(json_decode($response->getBody())); array ( 0 => array ( 'name' => 'select-1454375030381', 'label' => 'Ground or Whole Bean?', 'answer' => 'Whole Bean', ), )

GET /customer/addresses/{id}

Get a complete lists of a customer's addresses by passing in an integer representing the customer ID.

The general idea of the get addresses method is that it allows you to retrieve all of the additional addresses that a customer has added to their account. The most common use-case for this is if you want to allow your customers to ship to multiple locations, and want to keep a reference for additional shipping options.

Parameters

customer_id
integer Customer ID (required)
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/customer/addresses/54398');
Sample response:
var_export(json_decode($response->getBody())); array ( 0 => array ( 'user_id' => 1234, 'customer_id' => 54398, 'first_name' => 'Awesome', 'last_name' => 'Person', 'address' => '123, Easy Street', 'city' => 'Los Angeles', 'state' => 'California', 'zip' => '93003', 'country' => 'US', 'phone' => '4564564566', 'id' => 0, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', 'deleted_at' => NULL, ), 1 => array ( 'id' => 12, 'user_id' => 1234, 'customer_id' => 54398, 'first_name' => 'Joe', 'last_name' => 'Cool', 'phone' => '8978768768', 'address' => '123 Easy Street', 'city' => 'Dallas', 'state' => 'Texas', 'zip' => '93003', 'country' => 'US', 'metadata' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', 'deleted_at' => NULL, ), )

GET /customer/address/{id}

Get a single address by passing in an integer representing the address ID.

The general idea of the get address method is that it allows you to get a specific extra address a customer has added to their account. The most common use-case for this is if you want to allow your customers to ship to multiple locations, and want to keep a reference for additional shipping options.

Parameters

address_id
integer Address ID (required)
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/customer/address/12');
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => 12, 'user_id' => 1234, 'customer_id' => 54398, 'first_name' => 'Joe', 'last_name' => 'Cool', 'phone' => '8978768768', 'address' => '123 Easy Street', 'city' => 'Dallas', 'state' => 'Texas', 'zip' => '93003', 'country' => 'US', 'metadata' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', 'deleted_at' => NULL, )

POST /customer/address

Create an extra address for a customer.

Parameters

customer_id
integer Customer ID (required)
first_name
string First name (required)
last_name
string Last name (required)
address
string Street address (required)
city
string City (required)
state
string State / Region
zip
string Zip / Postal Code (required)
country
string Country Code (ex. US, GB, CA, AU, etc) (required)
phone
integer Phone number
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/customer/address', [ 'json' => [ 'customer_id' => 54398, 'first_name' => 'Awesome', 'last_name' => 'Person', 'address' => '123 Easy Street', 'city' => 'Los Angeles', 'state' => 'California', 'zip' => '93003', 'country' => 'US', 'phone' => '4564564566', ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => '1234546', 'user_id' => 1234, 'customer_id' => 54398, 'first_name' => 'Awesome', 'last_name' => 'Person', 'phone' => '4564564566', 'address' => '123 Easy Street', 'city' => 'Los Angeles', 'state' => 'California', 'zip' => '93003', 'country' => 'US', 'metadata' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', 'deleted_at' => NULL, )
;

PATCH /customer/address/{address_id}

Update an extra customer address.

Parameters

first_name
string First name
last_name
string Last name
address
string Street address
city
string City
state
string State / Region
zip
string Zip / Postal Code
country
string Country Code (ex. US, GB, CA, AU, etc)
phone
integer Phone number
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('PATCH', '/customer/address/12', [ 'json' => [ 'first_name' => 'Awesome', 'last_name' => 'Person', 'address' => '123 Easy Street', 'city' => 'Los Angeles', 'state' => 'California', 'zip' => '93003', 'country' => 'US', 'phone' => '4564564566', ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => 12, 'user_id' => 1234, 'customer_id' => 54398, 'first_name' => 'Awesome', 'last_name' => 'Person', 'phone' => '4564564566', 'address' => '123 Easy Street', 'city' => 'Los Angeles', 'state' => 'California', 'zip' => '93003', 'country' => 'US', 'metadata' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', 'deleted_at' => NULL, )
;

DELETE /customer/address/{address_id}

Delete an extra customer address

Parameters

Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('DELETE', '/customer/address/12');
Sample response:
var_export(json_decode($response->getBody())); array ( 'status' => 'success', )
;

GET /customer/profile/{id}

Get a complete customer profile (customer info, addresses, and profile answers) by using an integer representing the customer ID.

The general idea of the get profile method is that it allows you to retrieve all of the information associated with a given customer. The most common use-case for this is if you want to keep a copy of your customer's records, or if you want a more detailed overview of a customer than the get customers methods provides.

Parameters

customer_id
integer Customer ID (required)
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/customer/profile/54398');
Sample response:
var_export(json_decode($response->getBody())); array ( 'customer' => array ( 'id' => 54398, 'user_id' => 1234, 'first_name' => 'Awesome', 'last_name' => 'Person', 'email' => 'awesome+person1@example.com', 'phone' => '4564564566', 'address' => '123, Easy Street', 'city' => 'Los Angeles', 'state' => 'California', 'zip' => '93003', 'country' => 'US', 'gateway_reference' => '783789y6r890', 'default_card' => 34, 'gateway_type' => 'PaywhirlTest', 'gateway_id' => 18, 'currency' => 'USD', 'utm_source' => '', 'utm_medium' => '', 'utm_term' => '', 'utm_content' => '', 'utm_campaign' => '', 'utm_group' => '', 'metadata' => '', 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-06T11:24:00.000000Z', 'deleted_at' => NULL, ), 'addresses' => array ( 0 => array ( 'id' => 12, 'user_id' => 1234, 'customer_id' => 54398, 'first_name' => 'Joe', 'last_name' => 'Cool', 'phone' => '8978768768', 'address' => '123 Easy Street', 'city' => 'Dallas', 'state' => 'Texas', 'zip' => '93003', 'country' => 'US', 'metadata' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', 'deleted_at' => NULL, ), ), 'answers' => array ( 0 => array ( 'label' => 'Ground or Whole Bean?', 'answer' => 'Whole Bean', ), ), )

Plans

Plans can be considered one of the core products that PayWhirl manages for you. The general idea of a plan is that it establishes the "how much" and "when" to bill for the system, while also containing the "what" for the customer.

The PayWhirl plan page is the usual method for generating plans outside of this API, and can be a good resource for understanding each aspect of the plan object.

GET /plans

Get a list of plans created by your account.

The get plans method will return a list of up to 100 plans associated with your account. You can choose which slice of plans you want returned by using the before_id and after_id parameters. Each plan has a set of questions associated with it so that you can gather information on a per-plan basis.

Parameters

limit
integer Number of entries to return. Limit 100.
order_key
string Field to sort by. Default `id`.
order_direction
string Direction to order. (asc/desc) Defaults to `desc`.
before_id
integer All plans returned will have an id less than before_id.
after_id
integer All plans returned will have an id greater than after_id
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/plans/?limit=10&order_key=id&order_direction=desc');
Sample response:
var_export(json_decode($response->getBody())); array ( 0 => array ( 'id' => 64525, 'user_id' => 1234, 'active' => 0, 'enabled' => 0, 'description' => '', 'image' => '', 'name' => 'Super Order Plan', 'billing_amount' => 10, 'sku' => 'DEMO-SKU-2', 'currency' => 'USD', 'setup_fee' => 0, 'setup_fee_type' => NULL, 'setup_fee_sku' => NULL, 'require_tax' => 1, 'billing_interval' => 2, 'billing_frequency' => 'week', 'billing_dates' => '', 'billing_cycle_anchor' => 0, 'billing_cycle_anchor_time' => '', 'starting_day' => 0, 'trial_days' => 7, 'installments' => 0, 'require_shipping' => 1, 'cancellation_type' => NULL, 'autorenew_setup_fee' => 0, 'autorenew_plan' => 0, 'email_settings' => NULL, 'send_reminders' => NULL, 'questions' => '', 'display_download' => '', 'file' => '', 'tags' => '', 'metadata' => '', 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-06T11:24:00.000000Z', 'deleted_at' => NULL, ), 1 => array ( 'id' => 64524, 'user_id' => 1234, 'active' => 0, 'enabled' => 0, 'description' => '', 'image' => '', 'name' => 'Monthly Order Plan', 'billing_amount' => 10, 'sku' => 'DEMO-SKU-3', 'currency' => 'USD', 'setup_fee' => 0, 'setup_fee_type' => NULL, 'setup_fee_sku' => NULL, 'require_tax' => 1, 'billing_interval' => 1, 'billing_frequency' => 'month', 'billing_dates' => '', 'billing_cycle_anchor' => 0, 'billing_cycle_anchor_time' => '', 'starting_day' => 0, 'trial_days' => 0, 'installments' => 0, 'require_shipping' => 1, 'cancellation_type' => NULL, 'autorenew_setup_fee' => 0, 'autorenew_plan' => 0, 'email_settings' => NULL, 'send_reminders' => NULL, 'questions' => '', 'display_download' => '', 'file' => '', 'tags' => '', 'metadata' => '', 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-06T11:24:00.000000Z', 'deleted_at' => NULL, ), )

GET /plan/{id}

Get a single plan by plan ID.

As described above in the get plans section, plans are the core object that PayWhirl manages for you. This method is useful for inspecting the details of a specific plan so that you can either keep track of plans in your own local database, or so that you can have a base object to modify before updating the data in a plan.

If you need to find the plan id for retrieval of a specific plan, you can either check your plans page or use get plans to retrieve a larger list to select from.

Parameters

id
integer Plan ID
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/plan/64524');
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => 64524, 'user_id' => 1234, 'active' => 0, 'enabled' => 0, 'description' => '', 'image' => '', 'name' => 'Super Order Plan', 'billing_amount' => 10, 'sku' => 'DEMO-SKU-2', 'currency' => 'USD', 'setup_fee' => 0, 'setup_fee_type' => NULL, 'setup_fee_sku' => NULL, 'require_tax' => 1, 'billing_interval' => 2, 'billing_frequency' => 'week', 'billing_dates' => '', 'billing_cycle_anchor' => 0, 'billing_cycle_anchor_time' => '', 'starting_day' => 0, 'trial_days' => 7, 'installments' => 0, 'require_shipping' => 1, 'cancellation_type' => NULL, 'autorenew_setup_fee' => 0, 'autorenew_plan' => 0, 'email_settings' => NULL, 'send_reminders' => NULL, 'questions' => '', 'display_download' => '', 'file' => '', 'tags' => '', 'metadata' => '', 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-06T11:24:00.000000Z', 'deleted_at' => NULL, )

POST /create/plan

Create a plan that allows you to set rules for how your customers will be billed. Each time a customer subscribes to one of your plans, the rules of your plan will be attached to your subscription. If you change the rules for the plan, they will be applied to the customer's next invoice. Your plan will automatically be given an id with which you can then store and use later to update the plan if necessary.

This is generally the second step in setting up your PayWhirl account. The high level overview is that you create customers and plans, then they are linked together via a subscription.

The easiest way to get a feel for how to create a plan is to inspect the plan page

Parameters

name
string Plan name (required)
setup_fee
decimal Plan setup fee (default: 0.00)
setup_fee_type
string The type of setup fee invoice item to add to the order. Possible values:
  • product - Recurring Product
  • service - Recurring Service
  • recurring-fee - Recurring Fee
  • one-time-product - One-time Product
  • one-time-service - One-time Service
  • fee - One-time Fee
setup_fee_sku
string Plan setup fee sku added to the invoice (default: "")
installments
integer Number of times the customer should be billed. Use 0 for unlimited. (default: 0)
description
string Plan description (default: "")
installments
integer Number of times the customer should be billed. Use 0 for unlimited. (default: 0)
billing_amount
decimal Amount customer should be billed (required)
billing_interval
integer Interval in which billing should occur (default: 1)
billing_frequency
string day, week, month, year (default: month)
trial_days
integer Number of days for a free trial (default: 0)
sku
string Plan SKU (default: "")
currency
string Currency Code for Plan (default: USD)
require_tax
boolean Should the plan have tax rules applied? (default: 0)
require_shipping
boolean Should the plan have shipping rules applied? (default: 0)
billing_cycle_anchor
integer Day of the month billing should begin. Use 0 for date of signup. (default: 0)
enabled
boolean Enable to show plan in browse section of customer portal (default: 0)
tags
string Comma-separated list of tags to be added to the plan (default: empty)
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/create/plan', [ 'json' => [ 'name' => 'Super Order Plan', 'description' => '', 'setup_fee' => 0, 'setup_fee_type' => 'fee', 'setup_fee_sku' => '', 'installments' => 3, 'image' => '', 'starting_day' => 0, 'billing_amount' => 10, 'billing_interval' => 1, 'billing_frequency' => 'day', 'trial_days' => 0, 'currency' => 'USD', 'sku' => 'DEMO-SKU-2', 'require_tax' => 0, 'require_shipping' => 1, 'billing_cycle_anchor' => 0, 'enabled' => 0, 'tags' => '', ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => 7467, 'user_id' => 1234, 'active' => 0, 'enabled' => 0, 'description' => '', 'image' => '', 'name' => 'Super Order Plan', 'billing_amount' => 10, 'sku' => 'DEMO-SKU-2', 'currency' => 'USD', 'setup_fee' => 0, 'setup_fee_type' => 'fee', 'setup_fee_sku' => '', 'require_tax' => 0, 'billing_interval' => 1, 'billing_frequency' => 'day', 'billing_dates' => '', 'billing_cycle_anchor' => 0, 'billing_cycle_anchor_time' => '', 'starting_day' => 0, 'trial_days' => 0, 'installments' => 3, 'require_shipping' => 1, 'cancellation_type' => NULL, 'autorenew_setup_fee' => 0, 'autorenew_plan' => 0, 'email_settings' => NULL, 'send_reminders' => NULL, 'questions' => '', 'display_download' => '', 'file' => '', 'tags' => '', 'metadata' => '', 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', 'deleted_at' => NULL, )

POST /update/plan

To update a plan you simply pass in a plan object that has the values you wish to update, and ensure that the plan_id field matches the plan you wish to change.

The general use-case for updating a plan is that maybe the products you're offering now are higher quality, so the current customers' rates need to go up, or maybe you noticed an error in the original plan and need to modify it without creating a whole new plan and re-subscribing your customers. Any changes made to an existing plan will automatically be applied to any subscribed customers, so you don't need to worry about any additional modifications other than changing your plan.

The easiest way to update a plan is to use the get plan method if you know the plan_id, or the get plans method to retrieve a larger list to find your plan_id that needs changing. Once you have your plan object, simply modify the fields you wish to change and re-submit via update plan.

Parameters

id
integer Plan ID (required)
name
string Plan name
setup_fee
decimal Plan setup fee
setup_fee_type
string The type of setup fee invoice item to add to the order. Possible values:
  • product - Recurring Product
  • service - Recurring Service
  • recurring-fee - Recurring Fee
  • one-time-product - One-time Product
  • one-time-service - One-time Service
  • fee - One-time Fee
setup_fee_sku
string Plan setup fee sku added to the invoice (default: "")
installments
integer Number of times the customer should be billed. Use 0 for unlimited.
description
string Plan description
installments
integer Number of times the customer should be billed. Use 0 for unlimited.
billing_amount
decimal Amount customer should be billed
billing_interval
integer Interval in which billing should occur
billing_frequency
string day,week,month,year,dates
billing_dates
string When billing_frequency is dates, send a JSON-encoded array of specific dates of the year to be used instead of billing interval.
trial_days
integer Number of days for a free trial
sku
string Plan SKU
currency
string Currency Code for Plan
require_tax
boolean Should the plan have tax rules applied?
require_shipping
boolean Should the plan have shipping rules applied?
billing_cycle_anchor
integer Day of the month billing should begin. Use 0 for date of signup.
enabled
boolean Enable to show plan in browse section of customer portal
tags
string Comma-separated list of tags to be added to the plan
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/update/plan', [ 'json' => [ 'id' => 64524, 'name' => 'New plan name', 'setup_fee' => 0, 'setup_fee_type' => 'fee', 'setup_fee_sku' => '', 'installments' => 6, 'billing_amount' => 120, 'billing_interval' => 1, 'billing_frequency' => 'day', 'trial_days' => 0, 'sku' => 'e456456', 'currency' => 'USD', 'require_tax' => 0, 'require_shipping' => 1, 'billing_cycle_anchor' => 0, 'active' => 0, 'image' => '', 'starting_day' => 0, 'description' => '', 'enabled' => 0, 'tags' => '', ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => 64524, 'user_id' => 1234, 'active' => 0, 'enabled' => 0, 'description' => '', 'image' => '', 'name' => 'New plan name', 'billing_amount' => 120, 'sku' => 'e456456', 'currency' => 'USD', 'setup_fee' => 0, 'setup_fee_type' => 'fee', 'setup_fee_sku' => '', 'require_tax' => 0, 'billing_interval' => 1, 'billing_frequency' => 'day', 'billing_dates' => '', 'billing_cycle_anchor' => 0, 'billing_cycle_anchor_time' => '', 'starting_day' => 0, 'trial_days' => 0, 'installments' => 6, 'require_shipping' => 1, 'cancellation_type' => NULL, 'autorenew_setup_fee' => 0, 'autorenew_plan' => 0, 'email_settings' => NULL, 'send_reminders' => NULL, 'questions' => '', 'display_download' => '', 'file' => '', 'tags' => '', 'metadata' => '', 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', 'deleted_at' => NULL, )

Subscriptions

Subscriptions are the core objects that handle the billing process when you're not doing individual charges.

You create a subscription using the subscribe customer method, which requires that you have at least one customer with a payment method on file, as well as a plan you wish to subscribe the customer to. Once you've subscribed a customer, PayWhirl will generate invoices at regular intervals, which will then attempt to process payments at a time specified by your plans.

If you want up-to-date information about the status of your subscribers, see the webhooks section below to learn how to get feedback from our system.

GET /subscriptions/{id}

Get a list of subscriptions associated with a given customer.

This method is useful for determining which services a customer is or isn't currently expecting, which you can determine by checking value of current_period_end, which will be a Unix Timestamp (UTC time zone).

To find a customerID to look up, you'll want to check the get customers method for your language, which will return a list of all customers that you can inspect for the customer IDs. It may also be helpful to locally keep your customer IDs stored when you create them, as a large number of methods require a customer ID as input.

Parameters

id
integer Customer ID
status
string Leave empty or send "active" to get subscriptions without canceled for the customer; send "canceled" to get only canceled subscriptions for the customer; send "all" to get all the subscriptions for the customer (including canceled).
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/subscriptions/54398?status=all');
Sample response:
var_export(json_decode($response->getBody())); array ( 0 => array ( 'id' => 29654, 'user_id' => 1234, 'customer_id' => 54398, 'plan_id' => 64524, 'widget_id' => 0, 'quantity' => 1, 'trial_start' => 1709551440, 'trial_end' => 1710156240, 'current_period_start' => 1710156240, 'current_period_end' => 1711365840, 'installment_plan' => 1, 'installments_left' => 6, 'cancellation_type' => NULL, 'cancellation_requested_at' => NULL, 'cancel_at_period_end' => 0, 'vendor_product_id' => 0, 'parent_subscription_id' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-04-13T11:24:00.000000Z', 'deleted_at' => NULL, 'plan' => array ( 'id' => 64524, 'user_id' => 1234, 'active' => 0, 'enabled' => 0, 'description' => '', 'image' => '', 'name' => 'Super Order Plan', 'billing_amount' => 10, 'sku' => 'DEMO-SKU-2', 'currency' => 'USD', 'setup_fee' => 0, 'setup_fee_type' => NULL, 'setup_fee_sku' => NULL, 'require_tax' => 1, 'billing_interval' => 2, 'billing_frequency' => 'week', 'billing_dates' => '', 'billing_cycle_anchor' => 0, 'billing_cycle_anchor_time' => '', 'starting_day' => 0, 'trial_days' => 7, 'installments' => 0, 'require_shipping' => 1, 'cancellation_type' => NULL, 'autorenew_setup_fee' => 0, 'autorenew_plan' => 0, 'email_settings' => NULL, 'send_reminders' => NULL, 'questions' => '', 'display_download' => '', 'file' => '', 'tags' => '', 'metadata' => '', 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-06T11:24:00.000000Z', 'deleted_at' => NULL, ), ), )

GET /subscription/{id}

Get a single subscription based on the subscription's ID.

This method returns information about a specific subscription without displaying information about the customer or the plan. This can be useful if you're locally keeping track of subscription ids and want to find out which customers are associated with each plan, or if you need to know how long a subscription has left before you prompt for renewal.

The easiest way to keep track of the subscription IDs to pass in as a parameter is to record the id field whenever you subscribe a customer.

If you choose not to do this, you can find subscription IDs by using the get subscriptions method and passing in your customer IDs, or by using the get subscribers method.

Parameters
id
integer Subscription ID
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/subscription/29654');
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => 29654, 'user_id' => 1234, 'customer_id' => 54398, 'plan_id' => 64524, 'widget_id' => 0, 'quantity' => 1, 'trial_start' => 1709551440, 'trial_end' => 1710156240, 'current_period_start' => 1710156240, 'current_period_end' => 1711365840, 'installment_plan' => 1, 'installments_left' => 6, 'cancellation_type' => NULL, 'cancellation_requested_at' => NULL, 'cancel_at_period_end' => 0, 'vendor_product_id' => 0, 'parent_subscription_id' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-04-13T11:24:00.000000Z', 'deleted_at' => NULL, )

POST /subscribe/customer

Enroll a customer in an existing plan. The customer must have a valid payment method on file in order to be enrolled in a plan. Use the GET methods for customers and plans to retrieve your customer and plan IDs.

This is the fifth step described in the introduction, and is the method you'll use to begin charging your customers. The only required fields are customer id and plan id to create a subscription, but free trials are common for subscription services, and the code examples include a method for generating those.

The trial end field uses a Unix Timestamp in the UTC timezone, so you can also generate them using whatever the preferred method is for your system.

Email Settings: this method will trigger the New Subscription Email event if you've elected to create and use a new subscription template email.

The Payment Reminder and Payment Receipt emails are also tied to this method, as those emails are automatically sent when a transaction is about to process, or when a transaction actually processes (if you've chosen to use a template or to create your own emails.)

Unsuccessful Payment emails will also be sent if a customer with an active subscription's charge fails.

Parameters

customer_id
integer Customer ID (required)
plan_id
integer Plan ID (required)
trial_end
integer UNIX timestamp in the future
promo_id
integer ID of promo code to apply
quantity
integer Quantity (Default: 1)
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/subscribe/customer', [ 'json' => [ 'customer_id' => 54398, 'plan_id' => 64524, 'trial_end' => 1710156240, ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => 936537, 'user_id' => 1234, 'customer_id' => 54398, 'plan_id' => 64524, 'widget_id' => 0, 'quantity' => 1, 'trial_start' => 1709551440, 'trial_end' => 1710156240, 'current_period_start' => 1709551440, 'current_period_end' => 1710156240, 'installment_plan' => 0, 'installments_left' => 0, 'cancellation_type' => NULL, 'cancellation_requested_at' => NULL, 'cancel_at_period_end' => 0, 'vendor_product_id' => 0, 'parent_subscription_id' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', 'deleted_at' => NULL, )

POST /update/subscription

Update various subscription parameters. A customer's subscription will stay the same, but the payment plan referenced by that subscription will be modified.

This method is useful as an error-correction mechanism if your customer accidentally selected the wrong plan, or if you want to provide a method for changing customer plans without needing to destroy the whole transaction to create a new one. It can also be used to modify the various attributes of a subscription (below).

For clarity, the subscription id references the object that handles the timing and execution of the transactions, while the plan id references the object that determines the amount charged and the services provided. All the parameters listed below are optional, aside from the subscription ID. You may choose to update one or many of the subscription params.

Parameters

subscription_id
integer Subscription ID
plan_id
integer Plan ID
quantity
integer Quantity
address_id
integer Address ID to be associated with subscription invoices
installments_left
integer Number of installments left on a subscription (only for installment plans)
trial_end
integer UNIX timestamp of the time when trial period expires and subscription start charging. Set to zero to allow a subscription with trial date in the future to start immediately.
card_id
integer Card ID used as a payment method for this subscription's invoices
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/update/subscription', [ 'json' => [ 'subscription_id' => 29654, 'plan_id' => 64525, 'quantity' => 5, 'address_id' => 12345, 'trial_end' => 1710761040, 'card_id' => 123, ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => 29654, 'user_id' => 1234, 'customer_id' => 54398, 'plan_id' => 64525, 'widget_id' => 0, 'quantity' => 5, 'trial_start' => 1709551440, 'trial_end' => 1710761040, 'current_period_start' => 1710156240, 'current_period_end' => 1710761040, 'installment_plan' => false, 'installments_left' => 0, 'cancellation_type' => NULL, 'cancellation_requested_at' => NULL, 'cancel_at_period_end' => 0, 'vendor_product_id' => 0, 'parent_subscription_id' => NULL, 'created_at' => '2024-01-24T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', 'deleted_at' => NULL, )

POST /unsubscribe/customer

Cancel a customer's existing subscription.

This method will prevent the subscription from making any additional charges, and will unbind a customer from a plan.

It is important to note that this will cancel the subscription immediately, so if you plan to provide additional services during the time remaining on your customers' account, that will be the responsibility of your software. This method strictly prevents additional payments from being processed.

Email Settings: this method will trigger the Cancelled Subscription Email event if you've elected to create and use a cancelled subscription email.

Parameters

subscription_id
integer Subscription ID
customer_id
string Customer ID (can be used instead of subscription_id)
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/unsubscribe/customer', [ 'json' => [ 'subscription_id' => 29654, ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'status' => 'success', )

GET /subscribers

Get a list of active subscribers.

This method simply returns a list of customers that currently have an active subscription. This can be useful for grabbing a list of all customers that you need to provide services for.

The returned list is limited to 100 customers at a time, so it is important to use the starting_after and starting_before parameters to choose the slice you wish to work with.

Parameters

limit
integer Number of results to return. Defaults to 20, Max: 100
order
string Order of results. Values: asc, desc, rand
order_key
string Field to sort by.
order_direction
string Direction to order. (asc/desc) Defaults to `desc`.
starting_after
integer Show results with a subscription ID greater than starting_after
starting_before
integer Show results with a subscription ID less than starting_before
keyword
string (Optional) Filter by keyword
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/subscribers/?limit=10&order_key=current_period_end&order_direction=asc');
Sample response:
var_export(json_decode($response->getBody())); array ( 0 => array ( 'subscription_id' => 29654, 'user_id' => 1234, 'customer_id' => 54398, 'plan_id' => 64524, 'first_name' => 'Awesome', 'last_name' => 'Person', 'email' => 'awesome+person1@example.com', 'quantity' => 1, 'current_period_start' => 1710156240, 'current_period_end' => 1711365840, 'installment_plan' => 1, 'installments_left' => 6, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-04-13T11:24:00.000000Z', 'profile' => array ( 0 => array ( 'name' => 'select-1454375030381', 'label' => 'Ground or Whole Bean?', 'answer' => 'Whole Bean', ), ), ), )

Invoices

An invoice is an object that contains all the information needed to process a single transaction. You can think of a subscription as a sort of invoice factory, where each invoice produced will handle its own individual task.

While the subscription and charge methods handle the generation of invoices, the methods in this section allow you to view the contents of a single invoice, or to see all of the upcoming invoices for a single customer.

GET /invoice/{id}

Get a single invoice by passing in an invoice ID.

This method returns a single invoice indicating things like whether or not a customer has paid yet, their currency type, or how many attempts were made to collect the payment. These are useful to query for record keeping, or for keeping track of which customers still have outstanding fees.

The main ways to find your invoice IDs programmatically are to either use the get invoices method to get all invoices for a specific customer, or to record the data sent by the payment processing webhooks as described in the wehbooks section of this page. For one-off cases, you can also find your invoice ids in your invoices page.

Parameters

id
integer Invoice ID
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/invoice/39465');
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => 39465, 'user_id' => 1234, 'customer_id' => 54398, 'subscription_id' => 5047, 'charge_id' => 583, 'additional_charge_id' => NULL, 'card_id' => 365, 'address_id' => 0, 'amount_due' => 198, 'currency' => 'USD', 'discount_id' => 0, 'discount' => '0.00', 'shipping' => 0, 'shipping_total' => 0, 'tax' => 0, 'status' => 'Draft', 'paid' => 0, 'subtotal' => 198.98, 'attempted' => 0, 'attempt_count' => 0, 'due_date' => 0, 'next_payment_attempt' => 0, 'period_end' => 0, 'period_start' => 0, 'tax_total' => 0, 'tax_rule_name' => NULL, 'refunded_amount' => 0, 'webhooks_delivered_at' => 0, 'skipped_at' => NULL, 'paid_on' => 0, 'promo_id' => 0, 'promo_code' => '', 'promo_uses' => 0, 'integration_errors' => '', 'carrier' => '', 'service' => '', 'tracking_number' => '', 'is_order' => 1, 'shopify_created_at' => NULL, 'metadata' => NULL, 'tax_integration_id' => NULL, 'tax_calculated_at' => NULL, 'tax_status' => NULL, 'tax_date' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-05T11:24:00.000000Z', 'deleted_at' => NULL, 'profile' => array ( 0 => array ( 'name' => 'select-1454375030381', 'label' => 'Ground or Whole Bean?', 'answer' => 'Whole Bean', ), ), 'items' => array ( 0 => array ( 'id' => 2543, 'type' => 'plan', 'user_id' => 0, 'customer_id' => 0, 'invoice_id' => 39465, 'plan_id' => 64524, 'quantity' => 2, 'description' => 'Subscription to Coffee Daily 1', 'amount' => 99, 'currency' => 'USD', 'image' => NULL, 'tax_code' => NULL, 'sku' => '', 'vendor_product_id' => NULL, 'upsell_index' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-05T11:24:00.000000Z', 'deleted_at' => NULL, ), ), )

GET /invoices, /invoices/{customer_id}

Get a list of upcoming (or all) invoices

This method can be useful for determining which invoices will be processed soon, and for determining what a given customer is expecting. This is also a good place to retrieve information from any survey questions that are asked in ongoing services. Adding the all parameter will return all customer's invoices, not only the upcoming ones.

Parameters

customer_id
integer filters invoices by customer ID
all
integer 1 returns all invoices, 0 (or not submitted) returns only upcoming ones
tracking_number
string filters invoices by tracking number
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/invoices/54398?all=1');
Sample response:
var_export(json_decode($response->getBody())); array ( 0 => array ( 'id' => 12345, 'user_id' => 1234, 'customer_id' => 54398, 'subscription_id' => 0, 'charge_id' => 0, 'additional_charge_id' => NULL, 'card_id' => 0, 'address_id' => 12345, 'amount_due' => 198.98, 'currency' => 'USD', 'discount_id' => 0, 'discount' => '0.00', 'shipping' => 0, 'shipping_total' => 0, 'tax' => 0, 'status' => 'Draft', 'paid' => 0, 'subtotal' => 198.98, 'attempted' => 0, 'attempt_count' => 0, 'due_date' => 0, 'next_payment_attempt' => 0, 'period_end' => 0, 'period_start' => 0, 'tax_total' => 0, 'tax_rule_name' => NULL, 'refunded_amount' => 0, 'webhooks_delivered_at' => 0, 'skipped_at' => NULL, 'paid_on' => 0, 'promo_id' => 0, 'promo_code' => '', 'promo_uses' => 0, 'integration_errors' => '', 'carrier' => '', 'service' => '', 'tracking_number' => '', 'is_order' => 1, 'shopify_created_at' => NULL, 'metadata' => NULL, 'tax_integration_id' => NULL, 'tax_calculated_at' => NULL, 'tax_status' => NULL, 'tax_date' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-05T11:24:00.000000Z', 'deleted_at' => NULL, 'profile' => array ( 0 => array ( 'name' => 'select-1454375030381', 'label' => 'Ground or Whole Bean?', 'answer' => 'Whole Bean', ), ), 'items' => array ( 0 => array ( 'id' => 2543, 'type' => 'product', 'user_id' => 0, 'customer_id' => 0, 'invoice_id' => 12345, 'plan_id' => NULL, 'quantity' => 2, 'description' => 'Whole Bean', 'amount' => 9.99, 'currency' => 'USD', 'image' => NULL, 'tax_code' => NULL, 'sku' => '', 'vendor_product_id' => NULL, 'upsell_index' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-05T11:24:00.000000Z', 'deleted_at' => NULL, ), ), ), 1 => array ( 'id' => 12346, 'user_id' => 1234, 'customer_id' => 54398, 'subscription_id' => 0, 'charge_id' => 0, 'additional_charge_id' => NULL, 'card_id' => 0, 'address_id' => 12345, 'amount_due' => 198.98, 'currency' => 'USD', 'discount_id' => 0, 'discount' => '0.00', 'shipping' => 0, 'shipping_total' => 0, 'tax' => 0, 'status' => 'Draft', 'paid' => 0, 'subtotal' => 198.98, 'attempted' => 0, 'attempt_count' => 0, 'due_date' => 0, 'next_payment_attempt' => 0, 'period_end' => 0, 'period_start' => 0, 'tax_total' => 0, 'tax_rule_name' => NULL, 'refunded_amount' => 0, 'webhooks_delivered_at' => 0, 'skipped_at' => NULL, 'paid_on' => 0, 'promo_id' => 0, 'promo_code' => '', 'promo_uses' => 0, 'integration_errors' => '', 'carrier' => '', 'service' => '', 'tracking_number' => '', 'is_order' => 1, 'shopify_created_at' => NULL, 'metadata' => NULL, 'tax_integration_id' => NULL, 'tax_calculated_at' => NULL, 'tax_status' => NULL, 'tax_date' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-05T11:24:00.000000Z', 'deleted_at' => NULL, 'profile' => array ( 0 => array ( 'name' => 'select-1454375030381', 'label' => 'Ground or Whole Bean?', 'answer' => 'Whole Bean', ), ), 'items' => array ( 0 => array ( 'id' => 2544, 'type' => 'product', 'user_id' => 0, 'customer_id' => 0, 'invoice_id' => 12346, 'plan_id' => NULL, 'quantity' => 3, 'description' => 'Whole Bean', 'amount' => 99.99, 'currency' => 'USD', 'image' => NULL, 'tax_code' => NULL, 'sku' => '', 'vendor_product_id' => NULL, 'upsell_index' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-05T11:24:00.000000Z', 'deleted_at' => NULL, ), ), ), )

POST /invoice/{id}/process

Process a pending invoice by passing in an integer representing the invoice ID.

This method can be useful if you want to charge a customer before the automatic invoice processing time rolls around.

You can find invoice IDs with any of the methods illustrated in the get invoices section.

Payment methods requiring Strong Customer Authentication (SCA) need special handling when processing. This endpoint supports 2-step processing. Submit it normally without any additional parameters to try normal processing. This will use off_session flag and try to process invoice as if PayWhirl processes it in the backend. If your customer is present, submit customer_present = true flag as part of the POST request. This will tell PW to try on_session processing if off_session one requires authentication. If client-side auth is needed, this endpoint will return:
[ "requires_action" => true, "payment_intent_client_secret" => "pi_XYZ_secret_..." ]
You should continue with the Stripe JS SDK here and the returned intent client secret to authorize the payment. Upon successful authorization, call the same API endpoint with one additional parameter intent_id which comes as result.paymentIntent.id according to Stripe docs. This will trigger second step of processing the invoice which will finish and do any post-payment activities.

Parameters

id
integer the invoice_id you intend to process
customer_present
boolean submit as true to trigger on_session support for payments requiring Strong Customer Authentication
intent_id
string submit after customer has authenticated the payment intent, so server can finish invoice processing
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/invoice/39465/process', [ 'json' => [ 'id' => 39465, 'customer_present' => true, ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'status' => 'success', )

POST /invoice/{id}/mark-as-paid

Mark a pending invoice as paid by passing in an integer representing the invoice ID.

This method can be useful if you want to mark an invoice as paid without actually processing a charge transaction.

You can find invoice IDs with any of the methods illustrated in the get invoices section.

Parameters

id
integer the invoice_id you intend to process
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/invoice/39465/mark-as-paid');
Sample response:
var_export(json_decode($response->getBody())); array ( 'status' => 'success', )

POST /invoice/{id}/add-promo

Add a Promo Code to an existing unpaid invoice.

You can find invoice IDs with any of the methods illustrated in the get invoices section.

Parameters

id
integer the invoice_id you intend to process
promo_code
string promo code you want to apply
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/invoice/39465/add-promo', [ 'json' => [ 'promo_code' => 'PROMO-1234', ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'status' => 'success', )

POST /invoice/{id}/remove-promo

Remove existing Promo Code from an unpaid invoice.

You can find invoice IDs with any of the methods illustrated in the get invoices section.

Parameters

id
integer the invoice_id you intend to process
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/invoice/39465/remove-promo');
Sample response:
var_export(json_decode($response->getBody())); array ( 'status' => 'success', )

POST /invoice/{id}/card

Change invoice's payment method.

This method is useful for changing an upcoming invoice's payment method to another card that a customer has on file. The first parameter is an integer representing the ID of the invoice to modify, and the second parameter is the ID of the card you want to use for the invoice.

You can find invoice IDs with any of the methods illustrated in the get invoices section.

Parameters

id
integer the invoice_id you intend to modify
card_id
integer Payment method to change to.
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/invoice/39465/card', [ 'json' => [ 'card_id' => 9534, ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'status' => 'success', )

POST /invoice/{id}/items

Modifies the quantities of line items in a specific invoice.

This method is useful when you want to change the number of line items in an existing invoice. For example, invoice '22' might have items '1111' with a quantity of '2', and '1112' with a quantity of '3', but the customer wants '4' and '5' respectively. Instead of creating a brand new invoice, you can simply call this method and pass in (22, {'1111': 4, '1112': 5}), which will set the line item quantities to the updated values.

You can find invoice IDs with any of the methods illustrated in the get invoices section.

You can find the line item IDs by using the get invoices method above, and looking at the "items", where you will want the "id".

If you set any of the line_items to '0', the item will be deleted from the invoice.

Parameters

id
integer the invoice_id you intend to modify
line_items
object Dictionary or hash with { <line_item_id>: <quantity>, ... }
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/invoice/39465/items', [ 'json' => [ '2543' => 1, '2545' => 2, ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'status' => 'success', 'items_updated' => 2, )

POST /invoices

Create an upcoming or previously paid invoice for a customer.

Parameters

customer_id
integer Customer ID
next_payment_attempt
integer UNIX timestamp of the scheduled date of processing if creating an upcoming invoice. Must be in the future.
paid_on
integer UNIX timestamp of the date invoice was paid if creating a previously paid invoice. Must be in the past.
shipping
integer Charge shipping for this invoice.
tax
integer Charge tax for this invoice.
currency
string Invoice currency.
items
array Array of line items with keys: 'type', 'sku', 'quantity', 'description', 'amount', 'plan_id'
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/invoices', [ 'json' => [ 'customer_id' => 54398, 'next_payment_attempt' => 1709637840, 'shipping' => 1, 'tax' => 1, [ [ 'type' => 'product', 'description' => 'Sample item', 'amount' => 22.34, 'quantity' => 1, ] ] ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'status' => 'success', 'invoice_id' => 1, )

POST /delete/invoice

Delete an invoice based on its ID.

Parameters

id
integer Invoice ID
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/delete/invoice', [ 'json' => [ 'id' => 39465, ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'status' => 'success', 'invoice' => array ( 'id' => 39465, 'user_id' => 1234, 'customer_id' => 54398, 'subscription_id' => 5047, 'charge_id' => 583, 'additional_charge_id' => NULL, 'card_id' => 365, 'address_id' => 0, 'amount_due' => 198, 'currency' => 'USD', 'discount_id' => 0, 'discount' => '0.00', 'shipping' => 0, 'shipping_total' => 0, 'tax' => 0, 'status' => 'Draft', 'paid' => 0, 'subtotal' => 198.98, 'attempted' => 0, 'attempt_count' => 0, 'due_date' => 0, 'next_payment_attempt' => 0, 'period_end' => 0, 'period_start' => 0, 'tax_total' => 0, 'tax_rule_name' => NULL, 'refunded_amount' => 0, 'webhooks_delivered_at' => 0, 'skipped_at' => NULL, 'paid_on' => 0, 'promo_id' => 0, 'promo_code' => '', 'promo_uses' => 0, 'integration_errors' => '', 'carrier' => '', 'service' => '', 'tracking_number' => '', 'is_order' => 1, 'shopify_created_at' => NULL, 'metadata' => NULL, 'tax_integration_id' => NULL, 'tax_calculated_at' => NULL, 'tax_status' => NULL, 'tax_date' => NULL, 'created_at' => '2024-03-02T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', 'deleted_at' => '2024-03-04T11:24:00.000000Z', ), )

POST /invoices/{id}/next-payment-date

Updating the next payment date of the upcoming invoice.

Parameters

id
integer Invoice ID
next_payment_attempt
integer UNIX timestamp of the scheduled date of processing if creating an upcoming invoice. Must be in the future.
all
boolean (default: 0)

APPLY TO ALL FUTURE INVOICES
Setting this option to true WILL change the customer's billing cycle and future payment dates based on the chosen date.

.
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/invoices/39465/next-payment-date', [ 'json' => [ 'next_payment_attempt' => 1709551440, 'all' => true, ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => 39465, 'user_id' => 1234, 'customer_id' => 54398, 'subscription_id' => 0, 'charge_id' => 0, 'additional_charge_id' => NULL, 'card_id' => 0, 'address_id' => 0, 'amount_due' => 198, 'currency' => 'USD', 'discount_id' => 0, 'discount' => '0.00', 'shipping' => 0, 'shipping_total' => 0, 'tax' => 0, 'status' => 'Scheduled', 'paid' => 0, 'subtotal' => 198.98, 'attempted' => 0, 'attempt_count' => 0, 'due_date' => 0, 'next_payment_attempt' => 1709551440, 'period_end' => 1709551440, 'period_start' => 0, 'tax_total' => 0, 'tax_rule_name' => NULL, 'refunded_amount' => 0, 'webhooks_delivered_at' => 0, 'skipped_at' => NULL, 'paid_on' => 0, 'promo_id' => 0, 'promo_code' => '', 'promo_uses' => 0, 'integration_errors' => '', 'carrier' => '', 'service' => '', 'tracking_number' => '', 'is_order' => 1, 'shopify_created_at' => NULL, 'metadata' => NULL, 'tax_integration_id' => NULL, 'tax_calculated_at' => NULL, 'tax_status' => NULL, 'tax_date' => NULL, 'created_at' => '2024-03-03T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', 'deleted_at' => NULL, 'profile' => array ( 0 => array ( 'name' => 'select-1454375030381', 'label' => 'Ground or Whole Bean?', 'answer' => 'Whole Bean', ), ), 'items' => array ( 0 => array ( 'id' => 2543, 'type' => 'plan', 'user_id' => 0, 'customer_id' => 0, 'invoice_id' => 39465, 'plan_id' => 64524, 'quantity' => 2, 'description' => 'Subscription to Coffee Daily 1', 'amount' => 99, 'currency' => 'USD', 'image' => NULL, 'tax_code' => NULL, 'sku' => '', 'vendor_product_id' => NULL, 'upsell_index' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-05T11:24:00.000000Z', 'deleted_at' => NULL, ), ), )

Gateways

Payment gateways are where you'll set up your business and bank account information so that PayWhirl's automatic transactions can deposit funds into your bank account.

During the development and testing phase, the PayWhirl Test Gateway will allow you to use your plans and create charges without any actual transaction taking place, so you can verify the integrity of your system and experiment with the available methods.

The methods in this section allow you to view the status of all gateways associated with your account, or to check the information about a single gateway.

GET /gateways

Get a list of payment gateways associated with your account.

Parameters

Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/gateways/');
Sample response:
var_export(json_decode($response->getBody())); array ( 0 => array ( 'id' => 836, 'user_id' => 1234, 'name' => 'PayWhirl Test Gateway', 'type' => 'PaywhirlTest', 'error_message' => '', 'expires' => 0, 'metadata' => '', 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', ), 1 => array ( 'id' => 837, 'user_id' => 1234, 'name' => 'Stripe Connect', 'type' => 'StripeConnect', 'error_message' => '', 'expires' => 0, 'metadata' => '', 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', ), )

GET /gateway/{id}

Get a single payment gateway by ID.

Parameters

id
integer Gateway ID
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/gateway/836');
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => 836, 'user_id' => 1234, 'name' => 'PayWhirl Test Gateway', 'type' => 'PaywhirlTest', 'error_message' => '', 'expires' => 0, 'metadata' => '', 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', )

Charges

Sometimes it's useful to do a one-time charge, often as a signup fee, or so that you don't need to deal with multiple payment services for non-recurring transactions.

The charge methods offer a simple way to create an invoice with just a charge amount and a customer ID, and to review the contents of a single charge without looking at the entire invoice.

If you want to keep track of the individual charge id, it's returned after a create charge in items[index]->id.

You can also refund a charge by its id.

POST /create/charge

Create an invoice with a single charge.

Parameters

customer_id
integer (required)
amount
decimal (required)
quantity
integer (default: 1)
description
string (default: Charge)
schedule
UNIX timestamp defaults to now
sku
string product sku (optional)
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/create/charge', [ 'json' => [ 'customer_id' => 54398, 'amount' => 100.99, 'quantity' => 2, 'description' => 'Whole Bean', 'sku' => 'sku-123', 'schedule' => 1709555040, ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => 72464, 'user_id' => 1234, 'customer_id' => 54398, 'subscription_id' => 0, 'charge_id' => 0, 'additional_charge_id' => NULL, 'card_id' => 9534, 'address_id' => 0, 'amount_due' => 201.98, 'currency' => 'USD', 'discount_id' => 0, 'discount' => '0.00', 'shipping' => 0, 'shipping_total' => 0, 'tax' => 0, 'status' => 'Scheduled', 'paid' => 0, 'subtotal' => 201.98, 'attempted' => 0, 'attempt_count' => 0, 'due_date' => 0, 'next_payment_attempt' => 1709555040, 'period_end' => 1709555040, 'period_start' => 0, 'tax_total' => 0, 'tax_rule_name' => '', 'refunded_amount' => 0, 'webhooks_delivered_at' => 0, 'skipped_at' => NULL, 'paid_on' => 0, 'promo_id' => 0, 'promo_code' => '', 'promo_uses' => 0, 'integration_errors' => '', 'carrier' => '', 'service' => '', 'tracking_number' => '', 'is_order' => 1, 'shopify_created_at' => NULL, 'metadata' => NULL, 'tax_integration_id' => NULL, 'tax_calculated_at' => '2024-03-04 11:24:00', 'tax_status' => 'success', 'tax_date' => '2024-03-04 12:24:00', 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', 'deleted_at' => NULL, 'items' => array ( 0 => array ( 'id' => 365, 'type' => 'fee', 'user_id' => 1234, 'customer_id' => 54398, 'invoice_id' => 72464, 'plan_id' => NULL, 'quantity' => 2, 'description' => 'Whole Bean', 'amount' => 100.99, 'currency' => 'USD', 'image' => NULL, 'tax_code' => '', 'sku' => 'sku-123', 'vendor_product_id' => NULL, 'upsell_index' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', 'deleted_at' => NULL, ), ), )

GET /charge/{id}

Get a single charge using the charge ID.

Parameters

id
integer Charge ID
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/charge/342');
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => 342, 'user_id' => 1234, 'customer_id' => 54398, 'gateway_id' => 18, 'gateway_reference' => 'ch_171eGCKsdBiM5uJj5rY', 'amount' => 20.3, 'currency' => 'USD', 'usd' => '19.99', 'description' => 'Payment for Invoice #52', 'fee' => 0, 'bill_fee' => 0, 'fee_item_id' => '0', 'fee_refund_item_id' => '0', 'refunded' => 0, 'refunded_on' => NULL, 'refunded_amount' => 0, 'refund_reference' => '', 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', 'deleted_at' => NULL, )

POST /refund/charge/{id}

Refund a single charge with the whole or partial amount.

Parameters

id
integer Charge ID (required)
refund_amount
decimal Amount to refund up to the charge total amount. If left empty, the whole amount will be refunded. (optional)
mark_only
boolean If true, this will mark the charge as refunded only without sending the refund request to 3rd party gateway. Use it for internally handled charges on PayWhirl. (default: false)
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/refund/charge/342', [ 'json' => [ 'refund_amount' => '1.99', 'mark_only' => true, ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => 342, 'user_id' => 1234, 'customer_id' => 54398, 'gateway_id' => 18, 'gateway_reference' => 'ch_171eGCKsdBiM5uJj5rY', 'amount' => 20.3, 'currency' => 'USD', 'usd' => '19.99', 'description' => 'Payment for Invoice #52', 'fee' => 0, 'bill_fee' => 0, 'fee_item_id' => '0', 'fee_refund_item_id' => '0', 'refunded' => 1, 'refunded_on' => 1709551440, 'refunded_amount' => 1.99, 'refund_reference' => 'Manually marked as refunded.', 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', 'deleted_at' => NULL, )

Cards

The card object handles the storage and creation of credit card information, along with a reference to the payment gateway you want to use to process charges.

Because it's important never to store unencrypted card information, we only accept Stripe, Braintree, Authorize.net, or Square card tokens.

However, if you're using the PayWhirl Test Gateway when building a service, we do allow test cards to be entered manually.

GET /cards/{customer_id}

Get a list of cards associated with a given customer ID.

Parameters

customer_id
integer Customer ID
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/cards/54398');
Sample response:
var_export(json_decode($response->getBody())); array ( 0 => array ( 'id' => 34, 'user_id' => 1234, 'customer_id' => 54398, 'gateway_id' => 18, 'gateway_reference' => '783789y6r890', 'brand' => 'Visa', 'funding' => 'Credit', 'country' => 'US', 'last4' => '4242', 'exp_date' => '12/2032', 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', ), 1 => array ( 'id' => 35, 'user_id' => 1234, 'customer_id' => 54398, 'gateway_id' => 18, 'gateway_reference' => '783789y6r891', 'brand' => 'Mastercard', 'funding' => 'debit', 'country' => 'US', 'last4' => '4242', 'exp_date' => '12/2032', 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', ), )

GET /card/{id}

Get a single card by passing in the card's ID.

Parameters

id
integer Card ID
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/card/34');
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => 34, 'user_id' => 1234, 'customer_id' => 54398, 'gateway_id' => 18, 'gateway_reference' => '783789y6r890', 'brand' => 'Credit', 'funding' => 'Credit', 'country' => 'Ecuador', 'last4' => '4242', 'exp_date' => '12/2032', 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', )

POST /create/card

Create card information to store with a customer. To create a card, you need to supply an object containing customer information along with a Stripe, Braintree or Authorize.net token. If you are managing card information in your own software, you must ensure that you are adhering to the Payment Card Industry (PCI) security standards. Their documentation can be found here.

You will choose the token type based on the gateway being used. For a test gateway, you can supply test card information using the extra parameters below.

Stripe integrations, please check documentation on how to obtain a stripeToken and send that to the server.

Additionally, please check documentation on how to save card details requiring Strong Customer Authentication (SCA). This endpoint allows you to send paymentMethodId or paymentIntentId instead of stripeToken if you are using the new Stripe Payment Intents API.

Braintree integrations, please check documentation on how to obtain the payment method nonce and send that to the server.

Authorize.net integrations, please check documentation on how to obtain the dataDescriptor and dataValue tokens and send those to the server.

Square integrations, please check documentation on how to obtain a secure single-use token (nonce) and send that to the server.

Email Settings: this method will trigger the New Card Email event if you've elected to create and use a new card email.

Parameters

id
integer Customer ID (required)
stripeToken
string token if using Stripe tokens
paymentMethodId
string if using Stripe createPaymentMethod handler
paymentIntentId
string if using Stripe handleCardAction or family handlers
payment_method_nonce
string token if using Braintree
dataDescriptor
string token if using Authorize.net
dataValue
string token if using Authorize.net
token
string token if using 3rd party gateway
gateway
string gateway type if using 3rd party gateway
number
string Credit card number, for test gateways only
exp_month
string Expiration Month (mm format), for test gateways only
exp_year
string Expiration Year (yyyy format), for test gateways only
first_name
string First name on card, for test gateways only
last_name
string Last name on card, for test gateways only
address
string Billing street address, for test gateways only
city
string Billing city, for test gateways only
state
string Billing state / region, for test gateways only
zip
string Billing zip / postal code, for test gateways only
country
string Billing country, for test gateways only
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/create/card', [ 'json' => [ 'id' => 54398, 'number' => '4242424242424242', 'exp_month' => '12', 'exp_year' => '2024', 'first_name' => 'John', 'last_name' => 'Doe', 'address' => '123 Main St', 'city' => 'Springfield', 'state' => 'IL', 'zip' => '62701', 'country' => 'US', ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => '1234546', 'user_id' => 1234, 'customer_id' => 54398, 'gateway_id' => 18, 'gateway_reference' => '66ac9a3e6404f', 'brand' => 'Credit', 'funding' => 'Credit', 'country' => 'US', 'last4' => '4242', 'exp_date' => '12/2024', 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', )

POST /delete/card

Delete a card based on the card's unique card ID.

Parameters

id
integer Card ID
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/delete/card', [ 'json' => [ 'id' => 12353, ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'status' => 'success', )

Promo Codes

Promo codes allow you to specify special prices for limited amounts of time or for a certain number of users, without the need to create multiple temporary plans.

The methods in this section allow you to view, create, and delete promo codes.

A promo code object itself stores a reference to the plans it affects, so you might do something like create a 20% off code for the first 100 customers that use any of your plans.

GET /promo

Get a list of all promo codes on file.

Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/promo/');
Sample response:
var_export(json_decode($response->getBody())); array ( 0 => array ( 'id' => 836, 'user_id' => 1234, 'code' => 'PROMO-1234', 'amount_off' => NULL, 'percent_off' => 10, 'duration' => 'once', 'duration_uses' => 0, 'redeem_by' => 0, 'max_redemptions' => 0, 'times_redeemed' => 0, 'is_valid' => 1, 'one_use' => 0, 'apply_to' => 'grandtotal', 'plans' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', 'deleted_at' => NULL, ), 1 => array ( 'id' => 837, 'user_id' => 1234, 'code' => 'PROMO-9876', 'amount_off' => NULL, 'percent_off' => 10, 'duration' => 'once', 'duration_uses' => 0, 'redeem_by' => 0, 'max_redemptions' => 0, 'times_redeemed' => 0, 'is_valid' => 1, 'one_use' => 0, 'apply_to' => 'grandtotal', 'plans' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', 'deleted_at' => NULL, ), )

GET /promo/{id}

Get a promo code.

Parameters

id
integer/string Promo Code ID or code itself
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/promo/365');
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => 365, 'user_id' => 1234, 'code' => 'PROMO-1234', 'amount_off' => NULL, 'percent_off' => 10, 'duration' => 'once', 'duration_uses' => 0, 'redeem_by' => 0, 'max_redemptions' => 0, 'times_redeemed' => 0, 'is_valid' => 1, 'one_use' => 0, 'apply_to' => 'grandtotal', 'plans' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', 'deleted_at' => NULL, )

POST /create/promo

Create a promo code.

Parameters

code
string Promo code. Must use upper-case letter and numbers only.
duration
string How many times should this promo code be applied after a customer uses it? Must be one of: once, repeating, forever.
duration_uses
integer How many billing cycles should this be applied to?
amount_off
decimal A fixed amount to discount (Not needed if using percent off)
percent_off
integer A percentage of the order total to discount (Not needed if using amount off)
max_redemptions
integer How many customers can redeem this promo code before it is expired? (optional)
redeem_by
integer Expiration date of this promo code (optional). Must be a Unix timestamp, if specified.
apply_to
string Choose when to apply the promo code when calculating invoice totals: subtotal or grandtotal.
one_use
boolean If enabled, a customer can only redeem this code once.
plans
array Include plan IDs or registered upsell titles (from upsell widgets in your account). If specified, promo code will be applied only to those plans or upsells.
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/create/promo', [ 'json' => [ 'code' => '1STSUB20', 'duration' => 'once', 'percent_off' => 20, 'max_redemptions' => 300, 'redeem_by' => 1717500240, 'apply_to' => 'subtotal', 'one_use' => 1, ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => 7467, 'user_id' => 1234, 'code' => '1STSUB20', 'amount_off' => '0.00', 'percent_off' => 20, 'duration' => 'once', 'duration_uses' => 0, 'redeem_by' => 1717500240, 'max_redemptions' => 300, 'times_redeemed' => 0, 'is_valid' => 1, 'one_use' => 1, 'apply_to' => 'subtotal', 'plans' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', 'deleted_at' => NULL, )

POST /delete/promo

Delete a promo code by passing in the promo ID.

Parameters

id
integer Promo ID
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/delete/promo', [ 'json' => [ 'id' => 1234, ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'status' => 'success', )

Gift Codes

Gift Codes allow you to create objects that store a limited amount of value, which allow your customers to use their gift code balance instead of your regular currency.

GET /gift-codes

Get a list of all gift codes on file.

Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/gift-codes/');
Sample response:
var_export(json_decode($response->getBody())); array ( 0 => array ( 'id' => NULL, 'code' => '222-WKO-731', 'amount' => 10, 'remaining' => 8.99, 'currency' => 'USD', 'sender_id' => 71756, 'sender' => 'Thomas Tester', 'recipient_email' => 'recipient@example.com', 'redeemer_id' => 83595, 'redeemer' => 'Awesome Person', 'redeemer_email' => 'recipient@example.com', 'send_on' => NULL, 'redeemed_at' => NULL, 'gift_message' => 'A wonderful gift awaits!', 'sent_at' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', ), 1 => array ( 'id' => NULL, 'code' => '947-EFC-851', 'amount' => 15, 'remaining' => 15, 'currency' => 'USD', 'sender_id' => 71756, 'sender' => 'Thomas Tester', 'recipient_email' => 'recipient@example.com', 'redeemer_id' => 83595, 'redeemer' => 'Awesome Person', 'redeemer_email' => 'recipient@example.com', 'send_on' => NULL, 'redeemed_at' => NULL, 'gift_message' => 'Hey here is a present for you!', 'sent_at' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', ), )

Emails

The email section of the API just returns any emails you've created via the email-template page.

This is mostly used for displaying a page in your app that is identical to those you send to customers automatically when they make purchases or initially register.

For information on when your emails will be automatically sent, check your email settings page on your dashboard.

GET /email/{id}

Get a single email template via email ID. You can find your email ids at the end of the URL in the paywhirl email-template page. This functionality is mostly used to acquire copies of emails that are automatically sent on a variety of triggers. The template builder on the main paywhirl site is currently the best way to create and edit your templates.

Parameters

id
integer Email Template ID
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/email/453');
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => 453, 'user_id' => 1234, 'type' => '', 'name' => 'Welcome Email', 'from_name' => '', 'subject' => 'Welcome Aboard!', 'created_from' => 'welcome', 'template' => '', 'error_message' => '', 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', )

POST /send-email

Send a system generated email.

Email Settings: this method will trigger the corresponding email event if you've enabled it.

The parameters you pass in will be different depending on the email you want to send. The following list contains the basic structures that go with each type, while the parameter section below will indicate where to find specific ids or tokens.

  • "Welcome", customer_id
  • "NewSubscription", customer_id, invoice_id
  • "CancelledSubscription", customer_id, invoice_id
  • "NewCard", customer_id, card_id
  • "PaymentReminder", customer_id, invoice_id
  • "PaymentReceipt", customer_id, invoice_id
  • "PaymentRequest", customer_id, invoice_id
  • "ChargeFailed", customer_id, invoice_id
  • "PasswordReminder", customer_id, token (optional)
  • "RefundPayment", customer_id, charge_id
  • "ProfileUpdate", customer_id, changes[]
  • "RedeemGiftCode", customer_id, gift_code_id

Parameters

type
string (required) one of the following: Welcome, NewSubscription, CancelledSubscription, NewCard, PaymentReminder, PaymentReceipt, PaymentRequest, ChargeFailed, PasswordReminder, RefundPayment, ProfileUpdate, RedeemGiftCode
customer_id
string You can find a customer_id using get customers
invoice_id
string You can find an invoice_id using get invoices
subscription_id
string A subscription_id can be used instead of invoice_id param for NewSubscription and CancelledSubscription emails
card_id
string You can find a card_id using get cards
token
integer You can get a multiauth token using get multiauth token
charge_id
string You can find a charge_id using get charge
changes
array Array of changes for ProfileUpdate email, should contain the list of changes done to the profile
gift_code_id
string You can find a gift_code_id using get gift codes
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/send-email/', [ 'json' => [ 'type' => 'PasswordReminder', 'customer_id' => 54398, ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'status' => 'success', )

Accounts

The account object is our internal representation of you or your company.

The information contained here also has information related to your account settings, so you can use this to determine the state of each variable option in your dashboard without needing to log in to the app to check each submenu.

In other methods, you might also have noticed a user_id field, which refers to the id of the account holder (you or your business) rather than any of your users.

GET /account

Get information about the account of the user calling this method. (The account information returned is the account that matches the API credentials used to connect to the PayWhirl API.)

Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/account');
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => 1234, 'company_name' => 'Selling Business Things Inc.', 'email' => 'a.business.email@companyName.com', 'plan' => 'ultimate', 'trial_end' => 1710156240, 'first_name' => 'Business', 'last_name' => 'Person', 'address' => '', 'city' => '', 'country' => 'US', 'state' => '', 'phone' => '', 'zip' => '', 'gateway_id' => 1, 'currency' => 'USD', 'login_enabled' => 1, 'logo' => '', 'promo_codes' => 1, 'reg_enabled' => 1, 'retry_days' => 7, 'slug' => 'sell-business-things-inc', 'application_fee_percent' => 3, 'badge' => 'on', 'favicon' => NULL, 'timezone' => NULL, 'send_reminders' => 3, 'email_settings' => array ( 'test_mode' => 'off', 'welcome_email' => 'default', 'welcome_email_cc' => '', 'new_subscription' => 'default', 'new_subscription_cc' => '', 'cancel_subscription' => 'default', 'cancel_subscription_cc' => '', 'new_card' => 'default', 'new_card_cc' => '', 'payment_reminder' => 'default', 'payment_reminder_cc' => '', 'payment_receipt' => 'default', 'payment_receipt_cc' => '', 'payment_problem' => 'default', 'payment_problem_cc' => '', 'payment_request' => 'default', 'payment_request_cc' => '', 'password_reminder' => 'default', 'profile_update' => 'off', 'profile_update_cc' => '', 'email_via' => 'paywhirl', 'host' => '', 'username' => '', 'password' => '', 'from_address' => '', 'from_name' => '', 'port' => '', ), 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', )

GET /stats

Get invoice and revenue statistics about the account of the user calling this method. (The account information returned is the account that matches the API credentials used to connect to the PayWhirl API.)

Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/stats');
Sample response:
var_export(json_decode($response->getBody())); array ( 'plan' => 'ultimate', 'company_name' => 'Selling Business Things Inc.', 'logo' => '', 'active' => 1, 'trial_end' => '2024-03-11 11:24:00', 'customers' => 1, 'invoices' => 3, 'revenue_today' => '436.34', 'revenue_month' => '5892.79', 'revenue_total' => '29947.02', )

Shipping Rules

Shipping rules allow you to create and review shipping prices for items based on things like customer location or item price.

These are often used to handle covering international shipping costs, or to allow for deals like "free shipping on orders over $50".

Shipping rules can be created in the "Settings & Account" section of the PayWhirl dashboard.

GET /shipping

Get all shipping rules.

Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/shipping/');
Sample response:
var_export(json_decode($response->getBody())); array ( 0 => array ( 'id' => 365, 'user_id' => 1234, 'name' => 'Local Shipping', 'type' => 'in_location', 'plan_id' => 0, 'min_price' => 0, 'max_price' => 0, 'country' => 'CA', 'state' => '', 'zip' => '', 'shipping_price' => 5, 'zero_price' => 0, 'restrict_location' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', ), 1 => array ( 'id' => 366, 'user_id' => 1234, 'name' => 'International Shipping', 'type' => 'out_location', 'plan_id' => 0, 'min_price' => 0, 'max_price' => 0, 'country' => 'US', 'state' => '', 'zip' => '', 'shipping_price' => 15, 'zero_price' => 0, 'restrict_location' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', ), )

GET /shipping/{id}

Get a single shipping rule. Shipping rules can be created in the "Settings & Account" section of the PayWhirl dashboard.

Parameters

id
integer Shipping Rule ID
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/shipping/365');
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => 365, 'user_id' => 1234, 'name' => 'Local Shipping', 'type' => 'in_location', 'plan_id' => 0, 'min_price' => 0, 'max_price' => 0, 'country' => 'CA', 'state' => '', 'zip' => '', 'shipping_price' => 5, 'zero_price' => 0, 'restrict_location' => NULL, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', )

Tax Rules

Tax rules allow you to set specific percentages to charge based on customer location.

In general, you create a tax rule by choosing a name, a percentage, and a region. After the rules are made, customers in the set regions will automatically have the tax rules applied.

Tax rules can be created in the "Settings & Account" section of the PayWhirl dashboard.

GET /tax

Get all tax rules.

Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/tax/');
Sample response:
var_export(json_decode($response->getBody())); array ( 0 => array ( 'id' => 365, 'user_id' => 1234, 'name' => 'VAT', 'country' => 'GB', 'state' => '', 'zip' => '', 'tax_price' => 20, 'tax_shipping' => 0, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', ), 1 => array ( 'id' => 366, 'user_id' => 1234, 'name' => 'New York sales tax', 'country' => 'US', 'state' => 'New York', 'zip' => '', 'tax_price' => 4, 'tax_shipping' => 0, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', ), )

GET /tax/{id}

Get a single tax rule. Tax rules can be created in the "Settings & Account" section of the PayWhirl dashboard.

Parameters

id
integer Tax Rule ID
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/tax/365');
Sample response:
var_export(json_decode($response->getBody())); array ( 'id' => 365, 'user_id' => 1234, 'name' => 'VAT', 'country' => 'GB', 'state' => '', 'zip' => '', 'tax_price' => 20, 'tax_shipping' => 0, 'created_at' => '2024-03-04T11:24:00.000000Z', 'updated_at' => '2024-03-04T11:24:00.000000Z', )

MultiAuth Tokens

MultiAuth tokens are usually used as a method for allowing your customers to skip dealing with a PayWhirl login after they've decided to make a purchase. If you've chosen to embed PayWhirl widgets into your page or app, including a MultiAuth token in your widget script allows your customers to go straight from selecting an option directly to payment.

For other purposes, you can also use the MultiAuth tokens to redirect your customers directly to their carts, show them their payment schedules, and log them in via a PayWhirl button. Each of these options will be explained in slightly more detail below, after the rest make sense conceptually.

A MultiAuth token will expire at the end of the given duration, it will not be 'consumed' when used.

For the timestamp, you will want to use a UTC timestamp that is current_time + seconds_in_24_hours rather than seconds_in_24_hours.

POST /multiauth

Get a MultiAuth token to use to automatically login a customer to a widget.

The most common method for embedding a widget into an HTML page is to copy and paste your the script from the bottom of the page after selecting edit from this table. You can also find the table at by following this path from your dashboard.

Widgets & Forms > Manage Widgets > Edit

At the bottom of the edit page, there will be two embed codes that have the following form:

<script type="text/javascript"
        id='{your account id}'
        src="https://app.paywhirl.com/pw.js">
</script>

<script> paywhirl('widget',
                  {domain:'{your paywhirl domain}',
                   autoscroll: 1,
                   uuid:'{the uuid of your widget}'},
                  '{your account id}');
</script>

In general, copy-pasting the script from your widget page will work fine. But to add a login token that allows customers to skip that step because they're already logged in to your service, you need to use the get multiauth method to retrieve a token for whichever customer you want to log in, then you can use your favorite method to generate the following snippet:

<script type="text/javascript"
        id='{your account id}'
        src="https://app.paywhirl.com/pw.js">
</script>

<script> paywhirl('widget',
                  {domain:'{your paywhirl domain}',
                   multiauth: '{your multiauth_token}',
                   autoscroll: 1,
                   uuid:'{the uuid of your widget}'},
                  '{your account id}');
</script>

When you use the get multiauth method, a token is returned, along with the customer's ID and email address so you can verify the correct customer was selected before you generate a login code for them.

Another set of options is to replace the widget string in the examples with cart to skip directly to checkout; button to generate a small "Buy now with PayWhirl" button that will display your widgets in a new frame; or create to display the selected customer's payment calendar.

Parameters

id
integer ID of customer. Required if email not provided.
email
string Email of customer. Required if ID not provided.
expires
integer UNIX timestamp of when token should expire. Defaults to 60 seconds from now. Max token lifetime is 24 hours.
Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/multiauth', [ 'json' => [ 'id' => 54398, ] ]);
Sample response:
var_export(json_decode($response->getBody())); array ( 'multiauth_token' => 'raiI3...ZyI6IiJ9', 'email' => 'awesome+person37@example.com', 'id' => 54398, )

Webhooks

Webhooks will allow you to receive push notifications for your account. Certain third-party plugins will create them for you or ask your to register your webhooks URL. You can register a URL under the Developer tab on your PayWhirl account page. You can choose the event types you want to be notified for. The object to the right shows the format the data will be sent in.

As an optional security precaution, you may enter a signing secret to confirm the webhook notification was sent by PayWhirl. PayWhirl signs the full response HTTP body with a SHA256 HMAC hashing function using the secret you enter and sends the hash as "Paywhirl-Secret" HTTP header so you can validate against it.

For any fields with variable output types, there is a small explanation beneath the example webhook object on the right.

Events

customer.created
triggers whenever a customer is added to the account - via portal signup, widget checkout signup, or manually by admin or API.
customer.updated
triggers when the customer updates the profile from portal or admin updates customer details
plan.created
triggers when a plan is created by user or API
plan.updated
triggers when a plan is updated by user or API
card.created
triggers when a new payment method is added by customer in portal or checkout pages, by admin for a customer and via API.
card.deleted
triggers when a payment method is deleted by customer in portal, by admin, via the API or when a customer is deleted in the admin.
subscription.created
triggers when a customer is subscribed to a plan when invoices are created, before payment attempts - via widget checkout, manually by admin, or by the API. This triggers for each subsequent subscription that is created for chained plans.
subscription.updated
triggers when a customer or admin updates a subscription.
subscription.deleted
triggers when a subscription is canceled - after max failed payment attempts are reached or manually by admin, user (if allowed) or by API. Also triggered when held transactions are declined or refunded - a subscription is automatically canceled in that case. For installment subscriptions, this is triggered after the last installment payment is completed - the subscription is automatically canceled to prevent further charges. The only other case when this is triggered is for specific plan dates when a subscription would continued to a next date but no next date exists - e.g. incorrect specific dates setup like past dates.
invoice.created
triggers when an invoice is created - when subscription is created or renews and manually from admin or the API. The time that invoice is created is not always the time that it is processed as trial periods and processing schedule might offset the processing date.
invoice.processed
triggers when the invoice is processed (payment attempted) - this is usually automatic according to payment schedule but could be manually triggered by admin, user or API.
invoice.paid
triggers when the invoice is marked as paid. Can happen in 4 use cases:
1) invoice processes and payment is successful
2) admin manually marks invoice as paid
3) held charge is approved
4) held charge is refunded - invoice is marked as paid and then deleted as well as subscription canceled. So, another webhook with invoice.deleted and subscription.deleted will be sent.
invoice.deleted
triggers when admin manually deletes an invoice, when max failed attempts are reached and processing stops for this invoice and when held charge is declined or refunded.
charge.succeeded
triggers when a successful payment is made towards and invoice. This includes the cases when held transaction is approved. This webhook triggers when the transaction is approved, not when it is placed and held for review.
charge.failed
triggers when an invoice payment is declined for any reason (but not held for review). Also triggers when held transaction is declined.
charge.refunded
triggers manually when a refund is issued on PW side or automatically when Authorize.net or Stripe refund webhooks are received - i.e. when users refund transactions from payment gateway interfaces.
giftcode.sent
triggers when a gift code is sent to the recipient - might be immediately after creation or on a scheduled date.

Common field types and their values

card.brand, card.funding, card.gateway_reference
gateway-specific information about a payment method
customer.country
two-letter country code
customer.default_card
id of the default payment method for this customer
customer.gateway_type
StripeConnect, AuthorizeNet, Braintree, PayPal, PaywhirlTest, etc.
invoice.charge_id, invoice.additional_charge_id
id of the payment transaction (charge); additional_charge_id is present if there is a GiftCode payment along with a normal payment for this invoice
invoice.status
Subscribed, Attempting Payment, Paid, Refunded, Held. It can also be an error message returned by the gateway describing why the invoice failed processing.
invoice.shipping, invoice.tax
integer switch containing one or zero showing if shipping or taxes are calculated for this invoice
invoice.attempted
integer switch containing one or zero showing if the invoice has been attempted to be paid
invoice.attmept_count
how many times the invoice has been attempted to be paid
invoice.next_payment_attempt
unix timestamp of the moment when the next payment attempt will be made (+/- 1 minute)
invoice.period_start, invoice.period_end
unix timestamps of the period this invoice covers
invoice.paid
integer switch containing one or zero showing if the invoice is paid
invoice.paid_on
unix timestamp of the moment when the invoice was paid
invoice.amount_due
invoice grand total
plan.billing_frequency
month, week, day, year, dates
plan.billing_dates
JSON-encoded array of specific dates if plan is using specific dates rather than a recurring interval
plan.billing_cycle_anochor, plan.billing_cycle_anchor_time
day of the month and time of the day when the invoice is scheduled to charge
plan.require_shipping, plan.require_tax
integer switch containing one or zero; require_shipping might be -1 if shipping is required only on the first invoice
plan.enabled
integer switch containing one or zero - enables plan to show in customer portal and be bought without a widget
plan.installments
integer number describing the maximum number of installments a plan should charge
plan.autorenew_plan
plan_id to renew subscription with when the last installment finishes or zero to unsubscribe
plan.autorenew_setup_fee
integer switch contianing one or zero - whether to include setup fee when the subscription renews with another plan or not
subscription.current_period_start, subscription.current_period_end
unix timestamps of the period current subscription covers

Customer Created
The JSON object will have the following form:
{ "type": String, "customer": Object, "answers": Array.of(Object) }


Example:
{
  "type": "customer.created",
  "customer": {
    "id": 34,
    "user_id": 1,
    "first_name": "Awesome",
    "last_name": "Person",
    "email": "awesome+person@paywhirl.com",
    "phone": "4564564566",
    "address": "123 Easy Street",
    "city": "Los Angeles",
    "state": "California",
    "zip": 93003,
    "country": "US",
    "created_at": "2015-11-17 19:52:34",
    "updated_at": "2015-11-21 04:29:45",
    "gateway_reference": "783789y6r890",
    "default_card": 34,
    "gateway_type": "PayPal",
    "gateway_id": 18,
    "currency": "USD",
    "deleted_at": "",
    "utm_source": "Google",
    "utm_medium": "cpc",
    "utm_term": "online payments",
    "utm_content": "logolink",
    "utm_campaign": "spring_sale",
    "utm_group": "users",
    "ip_address":"76.176.160.450",
    "metadata": ""
  },
  "answers": [    
    {
      "label": "Is the sky blue?",
      "answer": "Yes"
    },
    {
      "label": "Are all skies on all planets blue?",
      "answer": "Maybe?"
    }
  ]
}


The "deleted_at" field will have the same timestamp format as "created_at"
and "updated_at".

The "metadata" field may contain a formatted JSON object:
{
  \"extra_data\":\"something\",
  \"more_extra_data\":1 
}

Customer Updated
The JSON object will have the following form:
{ "type": String, "customer": Object, "answers": array.of(Object) }


Example:
{
  "type": "customer.updated",
  "customer": {
    "id": 34,
    "user_id": 1,
    "first_name": "Awesome",
    "last_name": "Person",
    "email": "awesome+person@paywhirl.com",
    "phone": "4564564566",
    "address": "123 Easy Street",
    "city": "Los Angeles",
    "state": "California",
    "zip": 93003,
    "country": "US",
    "created_at": "2015-11-17 19:52:34",
    "updated_at": "2015-11-21 04:29:45",
    "gateway_reference": "783789y6r890",
    "default_card": 34,
    "gateway_type": "PayPal",
    "gateway_id": 18,
    "currency": "USD",
    "deleted_at": "",
    "utm_source": "Google",
    "utm_medium": "cpc",
    "utm_term": "online payments",
    "utm_content": "logolink",
    "utm_campaign": "spring_sale",
    "utm_group": "users",
    "ip_address":"76.176.160.450",
    "metadata": ""
  },
  "answers": [
    {
      "label": "Is the sky blue?",
      "answer": "Yes"
    },
    {
      "label": "Are all skies on all planets blue?",
      "answer": "Maybe?"
    }
  ]
}


The "deleted_at" field will have the same timestamp format as "created_at"
and "updated_at".

The "metadata" field may contain a formatted JSON object:
{
  \"extra_data\":\"something\",
  \"more_extra_data\":1 
}

Plan Created
The JSON object will have the following form:
{ "type": String, "plan": Object }


Example:
{
  "type": "plan.created",
  "plan": {
    "questions": "[{\"name\":\"checkbox-1513638794611\",\"repeated\":1}]",
    "name": "webhook test plan",
    "billing_amount": "0.01",
    "billing_interval": "1",
    "billing_dates": "[]",
    "billing_frequency": "month",
    "require_shipping": "1",
    "require_tax": "1",
    "trial_days": "5",
    "sku": "455KHU67",
    "setup_fee": "5.05",
    "autorenew_setup_fee": "0",
    "billing_cycle_anchor": "0",
    "billing_cycle_anchor_time": "",
    "currency": "USD",
    "enabled": "1",
    "installments": "0",
    "autorenew_plan": "0",
    "tags": "testplan, masterplan, goodplan?",
    "user_id": 1,
    "file": "",
    "updated_at": "2018-02-07 00:15:13",
    "created_at": "2018-02-07 00:15:13",
    "id": 41052
  }
}

If the "billing_frequency" field is set to "dates" you will see the following
form: 
{
  "billing_frequency": "dates",
  "billing_dates": "[\"2018-03-21\",\"2018-03-31\",\"2018-03-07\"]",
}

Plan Updated
The JSON object will have the following form:
{ "type": String, "plan": Object }


Example:
{
  "type": "plan.updated",
  "plan": {
    "id": 23,
    "user_id": 1,
    "name": "webhook test plan",
    "setup_fee": "5.05",
    "installments": "0",
    "require_shipping": "1",
    "active": 0,
    "image": "",
    "starting_day": 0,
    "description": "A fantastic plan!",
    "metadata": "{\"4277\":{\"place_orders\":\"all\"},\"4280\":{\"place_orders\":\"all\",\"customer_group\":\"none\"}}",
    "created_at": "2018-02-07 00:15:13",
    "updated_at": "2018-02-07 00:20:42",
    "billing_amount": "0.11",
    "billing_interval": "1",
    "billing_frequency": "month",
    "billing_dates": "[]",
    "trial_days": "0",
    "sku": "455KHU67",
    "currency": "USD",
    "require_tax": "1",
    "billing_cycle_anchor": "0",
    "billing_cycle_anchor_time": "",
    "deleted_at": null,
    "file": "",
    "autorenew_plan": "0",
    "tags": "testplan, masterplan, good... plan?",
    "enabled": "1",
    "questions": "[{\"name\":\"checkbox-1513638794611\",\"repeated\":1}]",
    "autorenew_setup_fee": "0"
  }
}

If the "billing_frequency" field is set to "dates" you will see the following
form: 
{
  "billing_frequency": "dates",
  "billing_dates": "[\"2018-03-21\",\"2018-03-31\",\"2018-03-07\"]",
}

Card Created
The JSON object will have the following form:
{ "type": String, "customer": Object, "card": Object }


Example:
{
  "type": "card.created",
  "customer": {
    "id": 34,
    "user_id": 1,
    "first_name": "Awesome",
    "last_name": "Person",
    "email": "awesome+person@paywhirl.com",
    "phone": "4564564566",
    "address": "123 Easy Street",
    "city": "Los Angeles",
    "state": "California",
    "zip": 93003,
    "country": "US",
    "created_at": "2015-11-17 19:52:34",
    "updated_at": "2015-11-21 04:29:45",
    "gateway_reference": "783789y6r890",
    "default_card": 34,
    "gateway_type": "PayPal",
    "gateway_id": 18,
    "currency": "USD",
    "deleted_at": "",
    "utm_source": "Google",
    "utm_medium": "cpc",
    "utm_term": "online payments",
    "utm_content": "logolink",
    "utm_campaign": "spring_sale",
    "utm_group": "users",
    "ip_address":"76.176.160.450",
    "metadata": ""
  },
  "card": {
    "gateway_reference": "5a7a47e944e3b",
    "brand": "Credit",
    "funding": "Credit",
    "country": "US",
    "customer_id": 29489320,
    "last4": "2222",
    "user_id": 1,
    "gateway_id": 18,
    "updated_at": "2018-02-07 00:27:21",
    "created_at": "2018-02-07 00:27:21",
    "id": 15
  }
}


The "deleted_at" field will have the same timestamp format as "created_at"
and "updated_at".

The "metadata" field may contain a formatted JSON object:
{
  \"extra_data\":\"something\",
  \"more_extra_data\":1
}

Card Deleted
The JSON object will have the following form:
{ "type": String, "customer": Object, "card": Object }


Example:
{
  "type": "card.deleted",
  "customer": {
    "id": 34,
    "user_id": 1,
    "first_name": "Awesome",
    "last_name": "Person",
    "email": "awesome+person@paywhirl.com",
    "phone": "4564564566",
    "address": "123 Easy Street",
    "city": "Los Angeles",
    "state": "California",
    "zip": 93003,
    "country": "US",
    "created_at": "2015-11-17 19:52:34",
    "updated_at": "2015-11-21 04:29:45",
    "gateway_reference": "783789y6r890",
    "default_card": 34,
    "gateway_type": "PayPal",
    "gateway_id": 18,
    "currency": "USD",
    "deleted_at": "2018-02-05 13:42:23",
    "utm_source": "Google",
    "utm_medium": "cpc",
    "utm_term": "online payments",
    "utm_content": "logolink",
    "utm_campaign": "spring_sale",
    "utm_group": "users",
    "ip_address":"76.176.160.450",
    "metadata": ""
  },
  "card": {
    "gateway_reference": "5a7a47e944e3b",
    "brand": "Credit",
    "funding": "Credit",
    "country": "US",
    "customer_id": 29489320,
    "last4": "2222",
    "user_id": 1,
    "gateway_id": 18,
    "updated_at": "2018-02-07 00:27:21",
    "created_at": "2018-02-07 00:27:21",
    "id": 15
  }
}


The "metadata" field may contain a formatted JSON object:
{
  \"extra_data\":\"something\",
  \"more_extra_data\":1
}

Subscription Created
The JSON object will have the following form:
{ "type": String, "customer": Object, "subscription": Object, "invoice": Object }

The "deleted_at" field will have the same timestamp format as "created_at"
and "updated_at".

Example:
{
  "type": "subscription.created",
  "customer": {
    "id": 34,
    "user_id": 1,
    "first_name": "Awesome",
    "last_name": "Person",
    "email": "awesome+person@paywhirl.com",
    "phone": "4564564566",
    "address": "123 Easy Street",
    "city": "Los Angeles",
    "state": "California",
    "zip": 93003,
    "country": "US",
    "created_at": "2015-11-17 19:52:34",
    "updated_at": "2015-11-21 04:29:45",
    "gateway_reference": "783789y6r890",
    "default_card": 34,
    "gateway_type": "PayPal",
    "gateway_id": 18,
    "currency": "USD",
    "deleted_at": "",
    "utm_source": "Google",
    "utm_medium": "cpc",
    "utm_term": "online payments",
    "utm_content": "logolink",
    "utm_campaign": "spring_sale",
    "utm_group": "users",
    "ip_address":"76.176.160.450",
    "metadata": ""
  },
  "subscription": {
    "user_id": 1,
    "customer_id": 36,
    "plan_id": 1,
    "widget_id": 0,
    "quantity": "1",
    "cancel_at_period_end": 0,
    "current_period_start": 1517963629,
    "current_period_end": 1517963629,
    "updated_at": "2018-02-07 00:33:49",
    "created_at": "2018-02-07 00:33:49",
    "id": 1337
  },
  "invoice": {
    "id": 461175,
    "user_id": 1,
    "customer_id": 27,
    "discount_id": 0,
    "subscription_id": 150700,
    "charge_id": 0,
    "additional_charge_id": null,
    "card_id": 273194,
    "shipping": 1,
    "tax": 1,
    "attempted": 0,
    "attempt_count": 0,
    "currency": "USD",
    "due_date": 0,
    "next_payment_attempt": 1517963629,
    "period_end": 1517963629,
    "period_start": 1517963629,
    "status": "Subscribed",
    "paid": 0,
    "subtotal": 0.11,
    "discount": "0.00",
    "shipping_total": 600,
    "tax_total": 0,
    "amount_due": 600.11,
    "webhooks_delivered_at": 0,
    "created_at": "2018-02-07 00:33:49",
    "updated_at": "2018-02-07 00:33:49",
    "paid_on": 0,
    "deleted_at": null,
    "promo_id": 0,
    "promo_code": "123PROMO",
    "promo_uses": 0,
    "integration_errors": "",
    "address_id": 0,
    "carrier": "UBS",
    "service": "SV",
    "tracking_number": "1Z 999 AA1 01 2345 6784",
    "is_order": 1,
    "shopify_created_at": null,
    "is_queued": 0
  }
}


The "metadata" field may contain a formatted JSON object:
{
  \"extra_data\":\"something\",
  \"more_extra_data\":1
} 

Subscription Updated
The JSON object will have the following form:
{ "type": String, "customer": Object, "subscription": Object, "invoice": Object, "order_address": Object }

The "deleted_at" field will have the same timestamp format as "created_at"
and "updated_at".

Example:
{
  "type": "subscription.updated",
  "customer": {
    "id": 34,
    "user_id": 1,
    "first_name": "Awesome",
    "last_name": "Person",
    "email": "awesome+person@paywhirl.com",
    "phone": "4564564566",
    "address": "123 Easy Street",
    "city": "Los Angeles",
    "state": "California",
    "zip": 93003,
    "country": "US",
    "created_at": "2015-11-17 19:52:34",
    "updated_at": "2015-11-21 04:29:45",
    "gateway_reference": "783789y6r890",
    "default_card": 34,
    "gateway_type": "PayPal",
    "gateway_id": 18,
    "currency": "USD",
    "deleted_at": "",
    "utm_source": "Google",
    "utm_medium": "cpc",
    "utm_term": "online payments",
    "utm_content": "logolink",
    "utm_campaign": "spring_sale",
    "utm_group": "users",
    "ip_address":"76.176.160.450",
    "metadata": ""
  },
  "subscription": {
    "user_id": 1,
    "customer_id": 36,
    "plan_id": 1,
    "widget_id": 0,
    "quantity": "1",
    "cancel_at_period_end": 0,
    "current_period_start": 1517963629,
    "current_period_end": 1517963629,
    "updated_at": "2018-02-07 00:33:49",
    "created_at": "2018-02-07 00:33:49",
    "id": 1337
  },
  "invoice": {
    "id": 461175,
    "user_id": 1,
    "customer_id": 27,
    "discount_id": 0,
    "subscription_id": 150700,
    "charge_id": 0,
    "additional_charge_id": null,
    "card_id": 273194,
    "shipping": 1,
    "tax": 1,
    "attempted": 0,
    "attempt_count": 0,
    "currency": "USD",
    "due_date": 0,
    "next_payment_attempt": 1517963629,
    "period_end": 1517963629,
    "period_start": 1517963629,
    "status": "Subscribed",
    "paid": 0,
    "subtotal": 0.11,
    "discount": "0.00",
    "shipping_total": 600,
    "tax_total": 0,
    "amount_due": 600.11,
    "webhooks_delivered_at": 0,
    "created_at": "2018-02-07 00:33:49",
    "updated_at": "2018-02-07 00:33:49",
    "paid_on": 0,
    "deleted_at": null,
    "promo_id": 0,
    "promo_code": "123PROMO",
    "promo_uses": 0,
    "integration_errors": "",
    "address_id": 0,
    "carrier": "UBS",
    "service": "SV",
    "tracking_number": "1Z 999 AA1 01 2345 6784",
    "is_order": 1,
    "shopify_created_at": null,
    "is_queued": 0
  },
  "order_address": {
    "first_name": "Terrelle",
    "last_name": "Suggs",
    "address": "3322 Golfers Dr",
    "city": "Oceanside",
    "state": "California",
    "zip": "92056",
    "country": "US",
    "phone": "3105555555"
  }
}


The "metadata" field may contain a formatted JSON object:
{
  \"extra_data\":\"something\",
  \"more_extra_data\":1
} 

Subscription Deleted
The JSON object will have the following form:
{ "type": String, "customer": Object, "subscription": Object, "invoice": Object }


Example:
{
  "type": "subscription.deleted",
  "customer": {
    "id": 34,
    "user_id": 1,
    "first_name": "Awesome",
    "last_name": "Person",
    "email": "awesome+person@paywhirl.com",
    "phone": "4564564566",
    "address": "123 Easy Street",
    "city": "Los Angeles",
    "state": "California",
    "zip": 93003,
    "country": "US",
    "created_at": "2015-11-17 19:52:34",
    "updated_at": "2015-11-21 04:29:45",
    "gateway_reference": "783789y6r890",
    "default_card": 34,
    "gateway_type": "PayPal",
    "gateway_id": 18,
    "currency": "USD",
    "deleted_at": "",
    "utm_source": "Google",
    "utm_medium": "cpc",
    "utm_term": "online payments",
    "utm_content": "logolink",
    "utm_campaign": "spring_sale",
    "utm_group": "users",
    "ip_address":"76.176.160.450",
    "metadata": ""
  },
  "subscription": {
    "id": 150700,
    "user_id": 1,
    "customer_id": 27,
    "plan_id": 41052,
    "widget_id": 0,
    "quantity": 1,
    "cancel_at_period_end": 0,
    "current_period_start": 1517963629,
    "current_period_end": 1520382829,
    "trial_start": 0,
    "trial_end": 0,
    "installment_plan": 0,
    "installments_left": 0,
    "created_at": "2018-02-07 00:33:49",
    "updated_at": "2018-02-07 00:33:49",
    "deleted_at": null
  },
  "invoice": {
    "id": 461175,
    "user_id": 1,
    "customer_id": 27,
    "discount_id": 0,
    "subscription_id": 150700,
    "charge_id": 361457,
    "additional_charge_id": null,
    "card_id": 273194,
    "shipping": 1,
    "tax": 1,
    "attempted": 1,
    "attempt_count": 1,
    "currency": "USD",
    "due_date": 0,
    "next_payment_attempt": 0,
    "period_end": 1517963629,
    "period_start": 1517963629,
    "status": "Paid",
    "paid": 1,
    "subtotal": 0.11,
    "discount": "0.00",
    "shipping_total": 600,
    "tax_total": 0,
    "amount_due": 600.11,
    "webhooks_delivered_at": 0,
    "created_at": "2018-02-07 00:33:49",
    "updated_at": "2018-02-07 00:33:53",
    "paid_on": 1517963629,
    "deleted_at": null,
    "promo_id": 0,
    "promo_code": "",
    "promo_uses": 0,
    "integration_errors": "null",
    "address_id": 0,
    "carrier": "UBS",
    "service": "SV",
    "tracking_number": "1Z 999 AA1 01 2345 6784",
    "is_order": 1,
    "shopify_created_at": "2018-02-07 00:33:52",
    "is_queued": 0
  }
}


The "deleted_at" field will have the same timestamp format as "created_at"
and "updated_at".

The "metadata" field may contain a formatted JSON object:
{
  \"extra_data\":\"something\",
  \"more_extra_data\":1
}

Invoice Created
The JSON object will have the following form:
{ "type": String, "customer": Object, "invoice": Object, "items": Array.of(Object), "answers": Array.of(Object) }


Example:
{
  "type": "invoice.created",
  "customer": {
    "id": 34,
    "user_id": 1,
    "first_name": "Awesome",
    "last_name": "Person",
    "email": "awesome+person@paywhirl.com",
    "phone": "4564564566",
    "address": "123 Easy Street",
    "city": "Los Angeles",
    "state": "California",
    "zip": 93003,
    "country": "US",
    "created_at": "2015-11-17 19:52:34",
    "updated_at": "2015-11-21 04:29:45",
    "gateway_reference": "783789y6r890",
    "default_card": 34,
    "gateway_type": "PayPal",
    "gateway_id": 18,
    "currency": "USD",
    "deleted_at": "",
    "utm_source": "Google",
    "utm_medium": "cpc",
    "utm_term": "online payments",
    "utm_content": "logolink",
    "utm_campaign": "spring_sale",
    "utm_group": "users",
    "ip_address":"76.176.160.450",
    "metadata": ""
  },
  "invoice": {
    "customer_id": 227913,
    "currency": "USD",
    "shipping": 1,
    "tax": 1,
    "status": "Subscribed",
    "period_start": 1517963629,
    "period_end": 1517963629,
    "next_payment_attempt": 1517963629,
    "card_id": 273194,
    "updated_at": "2018-02-07 00:33:49",
    "created_at": "2018-02-07 00:33:49",
    "id": 461175,
    "user_id": 6529,
    "subscription_id": 150700,
    "subtotal": 0.11,
    "discount": 0,
    "tax_total": 0,
    "shipping_total": 600,
    "amount_due": 600.11,
    "items": [
      {
        "id": 518501,
        "type": "plan",
        "user_id": 1,
        "customer_id": 27,
        "invoice_id": 461175,
        "quantity": 1,
        "description": "Subscription to webhook test plan",
        "amount": 0.11,
        "currency": "USD",
        "created_at": "2018-02-07 00:33:49",
        "updated_at": "2018-02-07 00:33:49",
        "deleted_at": null,
        "sku": "455KHU67"
      }
    ]
  },
  "answers": [
    {
      "label": "Is the sky blue?",
      "answer": "Yes"
    },
    {
      "label": "Are all skies on all planets blue?",
      "answer": "Maybe?"
    }
  ]
}


The "deleted_at" field will have the same timestamp format as "created_at"
and "updated_at".

The "metadata" field may contain a formatted JSON object:
{
  \"extra_data\":\"something\",
  \"more_extra_data\":1
}

Invoice Processed
The JSON object will have the following form:
{ "type": String, "customer": Object, "invoice": Object, "items": Array.of(Object) }


Example:
{
  "type": "invoice.processed",
  "customer": {
    "id": 34,
    "user_id": 1,
    "first_name": "Awesome",
    "last_name": "Person",
    "email": "awesome+person@paywhirl.com",
    "phone": "4564564566",
    "address": "123 Easy Street",
    "city": "Los Angeles",
    "state": "California",
    "zip": 93003,
    "country": "US",
    "created_at": "2015-11-17 19:52:34",
    "updated_at": "2015-11-21 04:29:45",
    "gateway_reference": "783789y6r890",
    "default_card": 34,
    "gateway_type": "PayPal",
    "gateway_id": 18,
    "currency": "USD",
    "deleted_at": "",
    "utm_source": "Google",
    "utm_medium": "cpc",
    "utm_term": "online payments",
    "utm_content": "logolink",
    "utm_campaign": "spring_sale",
    "utm_group": "users",
    "ip_address":"76.176.160.450",
    "metadata": ""
  },
  "invoice": {
    "id": 461175,
    "user_id": 1,
    "customer_id": 27,
    "discount_id": 0,
    "subscription_id": 150700,
    "charge_id": 0,
    "additional_charge_id": null,
    "card_id": 273194,
    "shipping": 1,
    "tax": 1,
    "attempted": 1,
    "attempt_count": 1,
    "currency": "USD",
    "due_date": 0,
    "next_payment_attempt": 1517963629,
    "period_end": 1517963629,
    "period_start": 1517963629,
    "status": "Attempting Payment",
    "paid": 0,
    "subtotal": 0.11,
    "discount": "0.00",
    "shipping_total": 600,
    "tax_total": 0,
    "amount_due": 600.11,
    "webhooks_delivered_at": 0,
    "created_at": "2018-02-07 00:33:49",
    "updated_at": "2018-02-07 00:33:49",
    "paid_on": 1517963629,
    "deleted_at": null,
    "promo_id": 0,
    "promo_code": "123PROMO",
    "promo_uses": 5,
    "integration_errors": "",
    "address_id": 0,
    "carrier": "UBS",
    "service": "SV",
    "tracking_number": "1Z 999 AA1 01 2345 6784",
    "is_order": 1,
    "shopify_created_at": null,
    "is_queued": 0,
    "items": [
      {
        "id": 518501,
        "type": "plan",
        "user_id": 6529,
        "customer_id": 227913,
        "invoice_id": 461175,
        "quantity": 1,
        "description": "Subscription to webhook test plan",
        "amount": 0.11,
        "currency": "USD",
        "created_at": "2018-02-07 00:33:49",
        "updated_at": "2018-02-07 00:33:49",
        "deleted_at": null,
        "sku": "455KHU67"
      }
    ]
  }
}


The "deleted_at" field will have the same timestamp format as "created_at"
and "updated_at".

The "metadata" field may contain a formatted JSON object:
{
  \"extra_data\":\"something\",
  \"more_extra_data\":1 
}

Invoice Paid
The JSON object will have the following form:
{ "type": String, "customer": Object, "invoice": Object, "items": Array.of(Object) }


Example:
{
  "type": "invoice.paid",
  "customer": {
    "id": 34,
    "user_id": 1,
    "first_name": "Awesome",
    "last_name": "Person",
    "email": "awesome+person@paywhirl.com",
    "phone": "4564564566",
    "address": "123 Easy Street",
    "city": "Los Angeles",
    "state": "California",
    "zip": 93003,
    "country": "US",
    "created_at": "2015-11-17 19:52:34",
    "updated_at": "2015-11-21 04:29:45",
    "gateway_reference": "783789y6r890",
    "default_card": 34,
    "gateway_type": "PayPal",
    "gateway_id": 18,
    "currency": "USD",
    "deleted_at": "",
    "utm_source": "Google",
    "utm_medium": "cpc",
    "utm_term": "online payments",
    "utm_content": "logolink",
    "utm_campaign": "spring_sale",
    "utm_group": "users",
    "ip_address":"76.176.160.450",
    "metadata": ""
  },
  "invoice": {
    "id": 461175,
    "user_id": 6529,
    "customer_id": 227913,
    "discount_id": 0,
    "subscription_id": 150700,
    "charge_id": 361457,
    "additional_charge_id": null,
    "card_id": 273194,
    "shipping": 1,
    "tax": 1,
    "attempted": 1,
    "attempt_count": 1,
    "currency": "USD",
    "due_date": 0,
    "next_payment_attempt": 0,
    "period_end": 1517963629,
    "period_start": 1517963629,
    "status": "Paid",
    "paid": 1,
    "subtotal": 0.11,
    "discount": "0.00",
    "shipping_total": 600,
    "tax_total": 0,
    "amount_due": 600.11,
    "webhooks_delivered_at": 0,
    "created_at": "2018-02-07 00:33:49",
    "updated_at": "2018-02-07 00:33:49",
    "paid_on": 1517963629,
    "deleted_at": null,
    "promo_id": 0,
    "promo_code": "",
    "promo_uses": 0,
    "integration_errors": "",
    "address_id": 0,
    "carrier": "UBS",
    "service": "SV",
    "tracking_number": "1Z 999 AA1 01 2345 6784",
    "is_order": 1,
    "shopify_created_at": null,
    "is_queued": 0,
    "items": [
      {
        "id": 518501,
        "type": "plan",
        "user_id": 6529,
        "customer_id": 227913,
        "invoice_id": 461175,
        "quantity": 1,
        "description": "Subscription to webhook test plan",
        "amount": 0.11,
        "currency": "USD",
        "created_at": "2018-02-07 00:33:49",
        "updated_at": "2018-02-07 00:33:49",
        "deleted_at": null,
        "sku": "455KHU67"
      }
    ]
  }
}


The "deleted_at" field will have the same timestamp format as "created_at"
and "updated_at".

The "metadata" field may contain a formatted JSON object:
{
  \"extra_data\":\"something\",
  \"more_extra_data\":1 
}

Charge Succeeded
The JSON object will have the following form:
{ "type": String, "customer": Object, "charge": Object, "invoice": Object }


Example:
{
  "type": "charge.succeeded",
  "customer": {
    "id": 34,
    "user_id": 1,
    "first_name": "Awesome",
    "last_name": "Person",
    "email": "awesome+person@paywhirl.com",
    "phone": "4564564566",
    "address": "123 Easy Street",
    "city": "Los Angeles",
    "state": "California",
    "zip": 93003,
    "country": "US",
    "created_at": "2015-11-17 19:52:34",
    "updated_at": "2015-11-21 04:29:45",
    "gateway_reference": "783789y6r890",
    "default_card": 34,
    "gateway_type": "PayPal",
    "gateway_id": 18,
    "currency": "USD",
    "deleted_at": "",
    "utm_source": "Google",
    "utm_medium": "cpc",
    "utm_term": "online payments",
    "utm_content": "logolink",
    "utm_campaign": "spring_sale",
    "utm_group": "users",
    "ip_address":"76.176.160.450",
    "metadata": ""
  },
  "charge": {
    "gateway_reference": "5a7a496d74572",
    "amount": 600.11,
    "currency": "USD",
    "customer_id": 227913,
    "description": "Payment for Invoice #461175",
    "user_id": 6529,
    "gateway_id": 11203,
    "usd": 600.11,
    "fee": 0,
    "bill_fee": 0,
    "updated_at": "2018-02-07 00:33:49",
    "created_at": "2018-02-07 00:33:49",
    "id": 361457
  },
  "invoice": {
    "id": 461175,
    "user_id": 1,
    "customer_id": 34,
    "discount_id": 0,
    "subscription_id": 150700,
    "charge_id": null,
    "additional_charge_id": null,
    "card_id": 273194,
    "shipping": 1,
    "tax": 1,
    "attempted": 1,
    "attempt_count": 1,
    "currency": "USD",
    "due_date": 0,
    "next_payment_attempt": 0,
    "period_end": 1517963629,
    "period_start": 1517963629,
    "status": "Paid",
    "paid": 1,
    "subtotal": 0.11,
    "discount": "0.00",
    "shipping_total": 600,
    "tax_total": 0,
    "amount_due": 600.11,
    "webhooks_delivered_at": 0,
    "created_at": "2018-02-07 00:33:49",
    "updated_at": "2018-02-07 00:33:53",
    "paid_on": 1517963629,
    "deleted_at": null,
    "promo_id": 0,
    "promo_code": "123PROMO",
    "promo_uses": 0,
    "integration_errors": "null",
    "address_id": 0,
    "carrier": "UBS",
    "service": "SV",
    "tracking_number": "1Z 999 AA1 01 2345 6784",
    "is_order": 1,
    "shopify_created_at": "2018-02-07 00:33:52",
    "is_queued": 0,
    "items": [
      {
        "id": 518501,
        "type": "plan",
        "user_id": 1,
        "customer_id": 34,
        "invoice_id": 461175,
        "quantity": 1,
        "description": "Subscription to webhook test plan",
        "amount": 0.11,
        "currency": "USD",
        "created_at": "2018-02-07 00:33:49",
        "updated_at": "2018-02-07 00:33:49",
        "deleted_at": null,
        "sku": "455KHU67"
      }
    ]
  }
}


The "deleted_at" field will have the same timestamp format as "created_at"
and "updated_at".

The "metadata" field may contain a formatted JSON object:
{
  \"extra_data\":\"something\",
  \"more_extra_data\":1 
}

Charge Failed
The JSON object will have the following form:
{ "type": String, "customer": Object, "invoice": Object }


Example:
{
  "type": "charge.failed",
  "customer": {
    "id": 34,
    "user_id": 1,
    "first_name": "Awesome",
    "last_name": "Person",
    "email": "awesome+person@paywhirl.com",
    "phone": "4564564566",
    "address": "123 Easy Street",
    "city": "Los Angeles",
    "state": "California",
    "zip": 93003,
    "country": "US",
    "created_at": "2015-11-17 19:52:34",
    "updated_at": "2015-11-21 04:29:45",
    "gateway_reference": "783789y6r890",
    "default_card": 34,
    "gateway_type": "PayPal",
    "gateway_id": 18,
    "currency": "USD",
    "deleted_at": "",
    "utm_source": "Google",
    "utm_medium": "cpc",
    "utm_term": "online payments",
    "utm_content": "logolink",
    "utm_campaign": "spring_sale",
    "utm_group": "users",
    "ip_address":"76.176.160.450",
    "metadata": ""
  },
  "invoice": {
    "id": 461175,
    "user_id": 1,
    "customer_id": 227913,
    "discount_id": 0,
    "subscription_id": 150700,
    "charge_id": 0,
    "additional_charge_id": null,
    "card_id": 0,
    "shipping": 0,
    "tax": 0,
    "attempted": 1,
    "attempt_count": 0,
    "currency": "USD",
    "due_date": 0,
    "next_payment_attempt": 1517966629,
    "period_end": 1517963629,
    "period_start": 1517963629,
    "status": "Cannot charge a customer that has no active card",
    "paid": 0,
    "subtotal": 0.11,
    "discount": "0.00",
    "shipping_total": 0,
    "tax_total": 0,
    "amount_due": 0.11,
    "webhooks_delivered_at": 0,
    "created_at": "2018-02-07 00:33:49",
    "updated_at": "2018-02-07 00:33:53",
    "paid_on": 0,
    "deleted_at": null,
    "promo_id": 0,
    "promo_code": "",
    "promo_uses": 0,
    "integration_errors": "",
    "address_id": 0,
    "carrier": "",
    "service": "",
    "tracking_number": "",
    "is_order": 1,
    "shopify_created_at": 0,
    "is_queued": 0,
    "items": [
      {
        "id": 518501,
        "type": "plan",
        "user_id": 1,
        "customer_id": 227913,
        "invoice_id": 461175,
        "quantity": 1,
        "description": "Subscription to webhook test plan",
        "amount": 0.11,
        "currency": "USD",
        "created_at": "2018-02-07 00:33:49",
        "updated_at": "2018-02-07 00:33:49",
        "deleted_at": null,
        "sku": "455KHU67"
      }
    ]
  },
  "subscription": {
    "user_id": 1,
    "customer_id": 227913,
    "plan_id": 1,
    "widget_id": 0,
    "quantity": "1",
    "cancel_at_period_end": 0,
    "current_period_start": 1517963629,
    "current_period_end": 1517963629,
    "updated_at": "2018-02-07 00:33:49",
    "created_at": "2018-02-07 00:33:49",
    "id": 150700
  },
  "plan": {
    "questions": "[{\"name\":\"checkbox-1513638794611\",\"repeated\":1}]",
    "name": "webhook test plan",
    "billing_amount": "0.01",
    "billing_interval": "1",
    "billing_dates": "[]",
    "billing_frequency": "month",
    "require_shipping": "1",
    "require_tax": "1",
    "trial_days": "5",
    "sku": "455KHU67",
    "setup_fee": "5.05",
    "autorenew_setup_fee": "0",
    "billing_cycle_anchor": "0",
    "billing_cycle_anchor_time": "",
    "currency": "USD",
    "enabled": "1",
    "installments": "0",
    "autorenew_plan": "0",
    "tags": "testplan, masterplan, goodplan?",
    "user_id": 1,
    "file": "",
    "updated_at": "2018-02-07 00:15:13",
    "created_at": "2018-02-07 00:15:13",
    "id": 1
  }
}


The "deleted_at" field will have the same timestamp format as "created_at"
and "updated_at".

The "metadata" field may contain a formatted JSON object:
{
  \"extra_data\":\"something\",
  \"more_extra_data\":1 
}

Charge Refunded
The JSON object will have the following form:
{ "type": String, "customer": Object, "charge": Object, "invoice": Object, "items": Array.of(Object) }


Example:
{
  "type": "charge.refunded",
  "customer": {
    "id": 34,
    "user_id": 1,
    "first_name": "Awesome",
    "last_name": "Person",
    "email": "awesome+person@paywhirl.com",
    "phone": "4564564566",
    "address": "123 Easy Street",
    "city": "Los Angeles",
    "state": "California",
    "zip": 93003,
    "country": "US",
    "created_at": "2015-11-17 19:52:34",
    "updated_at": "2015-11-21 04:29:45",
    "gateway_reference": "783789y6r890",
    "default_card": 34,
    "gateway_type": "PayPal",
    "gateway_id": 18,
    "currency": "USD",
    "deleted_at": "",
    "utm_source": "Google",
    "utm_medium": "cpc",
    "utm_term": "online payments",
    "utm_content": "logolink",
    "utm_campaign": "spring_sale",
    "utm_group": "users",
    "ip_address":"76.176.160.450",
    "metadata": ""
  },
  "charge": {
    "id": 361457,
    "user_id": 1,
    "gateway_reference": "5a7a496d74572",
    "amount": 600.11,
    "currency": "USD",
    "customer_id": 227913,
    "description": "Payment for Invoice #461175",
    "created_at": "2018-02-07 00:33:49",
    "updated_at": "2018-02-07 00:39:58",
    "gateway_id": 11203,
    "refunded": 1,
    "refund_reference": "5a7a4ade245e6",
    "fee": 0,
    "bill_fee": 0,
    "fee_item_id": 0,
    "fee_refund_item_id": 0,
    "deleted_at": null,
    "usd": "600.11"
  },
  "invoice": {
    "id": 461175,
    "user_id": 1,
    "customer_id": 227913,
    "discount_id": 0,
    "subscription_id": 150700,
    "charge_id": 361457,
    "additional_charge_id": null,
    "card_id": 273194,
    "shipping": 1,
    "tax": 1,
    "attempted": 1,
    "attempt_count": 1,
    "currency": "USD",
    "due_date": 0,
    "next_payment_attempt": 0,
    "period_end": 1517963629,
    "period_start": 1517963629,
    "status": "Paid",
    "paid": 1,
    "subtotal": 0.11,
    "discount": "0.00",
    "shipping_total": 600,
    "tax_total": 0,
    "amount_due": 600.11,
    "webhooks_delivered_at": 0,
    "created_at": "2018-02-07 00:33:49",
    "updated_at": "2018-02-07 00:33:53",
    "paid_on": 1517963629,
    "deleted_at": null,
    "promo_id": 0,
    "promo_code": "123PROMO",
    "promo_uses": 0,
    "integration_errors": "null",
    "address_id": 0,
    "carrier": "UBS",
    "service": "SV",
    "tracking_number": "1Z 999 AA1 01 2345 6784",
    "is_order": 1,
    "shopify_created_at": "2018-02-07 00:33:52",
    "is_queued": 0,
    "items": [
      {
        "id": 518501,
        "type": "plan",
        "user_id": 1,
        "customer_id": 227913,
        "invoice_id": 461175,
        "quantity": 1,
        "description": "Subscription to webhook test plan",
        "amount": 0.11,
        "currency": "USD",
        "created_at": "2018-02-07 00:33:49",
        "updated_at": "2018-02-07 00:33:49",
        "deleted_at": null,
        "sku": "455KHU67"
      }
    ]
  }


The "deleted_at" field will have the same timestamp format as "created_at"
and "updated_at".

The "metadata" field may contain a formatted JSON object:
{
  \"extra_data\":\"something\",
  \"more_extra_data\":1 
}

Gift Code Sent
The JSON object will have the following form:
{ "type": String, "customer": Object, "giftcode": Object, "invoice": Object, "items": Array.of(Object) }


Example:
{
  "type": "giftcode.sent",
  "customer": {
    id: 34,
    user_id: 1,
    first_name: 'Awesome',
    last_name: 'Person',
    email: 'awesome+person@paywhirl.com',
    phone: '4564564566',
    address: '123 Easy Street',
    city: 'Los Angeles',
    state: 'California',
    zip: 93003,
    country: 'US',
    created_at: '2015-11-17 19:52:34',
    updated_at: '2015-11-21 04:29:45',
    gateway_reference: '783789y6r890',
    default_card: 34,
    gateway_type: 'PayPal',
    gateway_id: 18,
    currency: 'USD',
    deleted_at: '',
    utm_source: 'Google',
    utm_medium: 'cpc',
    utm_term: 'online payments',
    utm_content: 'logolink',
    utm_campaign: 'spring_sale',
    utm_group: 'users',
    "ip_address":"76.176.160.450",
    metadata: ''
  },
  "giftcode": {
    "id": 2,
    "user_id": 1,
    "customer_id": 34,
    "invoice_item_id": 7880,
    "redeemed_by": null,
    "redeemed_at": null,
    "code": "947-EFC-851",
    "amount": 20.00,
    "remaining": 20.00,
    "currency": "USD",
    "gift_message": "A present for you!",
    "recipient_email": "friend@paywhirl.com",
    "email_template": null,
    "redeem_url": null,
    "send_on": null,
    "sent_at": "2017-10-27 08:22:11",
    "created_at": "2017-10-25 12:05:45",
    "updated_at": "2017-10-27 08:22:11"
  },
  "invoice": {
    "id": 461175,
    "user_id": 1,
    "customer_id": 227913,
    "discount_id": 0,
    "subscription_id": 150700,
    "charge_id": 361457,
    "additional_charge_id": null,
    "card_id": 273194,
    "shipping": 1,
    "tax": 1,
    "attempted": 1,
    "attempt_count": 1,
    "currency": "USD",
    "due_date": 0,
    "next_payment_attempt": 0,
    "period_end": 1517963629,
    "period_start": 1517963629,
    "status": "Paid",
    "paid": 1,
    "subtotal": 0.11,
    "discount": "0.00",
    "shipping_total": 600,
    "tax_total": 0,
    "amount_due": 600.11,
    "webhooks_delivered_at": 0,
    "created_at": "2018-02-07 00:33:49",
    "updated_at": "2018-02-07 00:33:53",
    "paid_on": 1517963629,
    "deleted_at": null,
    "promo_id": 0,
    "promo_code": "123PROMO",
    "promo_uses": 0,
    "integration_errors": "null",
    "address_id": 0,
    "carrier": "UBS",
    "service": "SV",
    "tracking_number": "1Z 999 AA1 01 2345 6784",
    "is_order": 1,
    "shopify_created_at": "2018-02-07 00:33:52",
    "is_queued": 0,
    "items": [
      {
        "id": 518501,
        "type": "plan",
        "user_id": 1,
        "customer_id": 227913,
        "invoice_id": 461175,
        "quantity": 1,
        "description": "Subscription to webhook test plan",
        "amount": 0.11,
        "currency": "USD",
        "created_at": "2018-02-07 00:33:49",
        "updated_at": "2018-02-07 00:33:49",
        "deleted_at": null,
        "sku": "455KHU67"
      }
    ]
  }


The "deleted_at" field will have the same timestamp format as "created_at"
and "updated_at".

The "metadata" field may contain a formatted JSON object:
{
  \"extra_data\":\"something\",
  \"more_extra_data\":1 
}