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.

GET /test

Verify that your API credentials are working.

Returns

A "connection successful" message.

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', )

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.

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

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.

Parameters

limit
integer (min: 1, max: 100)
default: 10
Number of entries to return.
order_key
string (one of: id, created_at, updated_at, first_name, last_name, email, gateway_id)
default: id
Field to sort by.
order_direction
string (one of: asc, desc)
default: desc
Sort direction.
before_id
integer Return only customers with ID less than this value.
after_id
integer Return only customers with ID greater than this value.
keyword
string Filter customers by first_name, last_name, or email.

Returns

An array of Customer objects.

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' => '{"origin":"api"}', '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' => '{"origin":"api"}', '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.

Parameters

id
integer in path required PayWhirl customer ID.

Returns

A Customer object.

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' => '{"origin":"api"}', '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 required
password
string 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 at least 6 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 required
last_name
string required
email
string required Customer email address.
currency
string required
example: USD
ISO currency code.
password
string
phone
string
address
string Street address.
city
string
state
string State / region.
zip
string
country
string
example: US
ISO country code.
gateway_id
integer 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

Returns

A Customer object.

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' => '{"origin":"api"}', '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. Only the fields you include are updated; fields you omit are left unchanged.

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
integer required PayWhirl customer ID.
first_name
string
last_name
string
email
string
password
string
phone
string
address
string Street address.
city
string
state
string State / region.
zip
string
country
string
example: US
ISO country code.
currency
string
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

Returns

A Customer object.

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' => '{"origin":"api"}', '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. This also erases the customer's personal data.

Parameters

id
integer required PayWhirl customer 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/customer', [ 'json' => [ 'id' => 54398, ] ]);
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 (min: 1, max: 100)
default: 100
Number of records to return.

Returns

An array of Question objects.

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 required The question's name/key.
customer_id
integer required
answer
string required Answer value.

Returns

An array of Profile Answer objects.

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 required

Returns

An array of Profile Answer objects.

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/{customer_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 in path required PayWhirl customer ID.

Returns

An array of Address objects.

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

id
integer in path required Address ID.

Returns

An Address object.

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 required
first_name
string required
last_name
string required
address
string required Street address.
city
string required
zip
string required
country
string required
example: US
ISO country code.
state
string State / region.
phone
string

Returns

An Address object.

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/{id}

Update an extra customer address.

Parameters

id
integer in path required Address ID.
first_name
string
last_name
string
address
string Street address.
city
string
state
string State / region.
zip
string
country
string
example: US
ISO country code.
phone
string

Returns

An Address object.

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/{id}

Delete an extra customer address

Parameters

id
integer in path required Address ID.
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/{customer_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 in path required PayWhirl customer ID.

Returns

A Customer Profile object.

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' => '{"origin":"api"}', '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 (min: 1, max: 100)
default: 10
Number of entries to return.
order_key
string (one of: user_id, name, setup_fee, setup_fee_type, setup_fee_sku, installments, description, billing_amount, billing_interval, billing_frequency, billing_dates, trial_days, sku, currency, require_tax, require_shipping, billing_cycle_anchor, billing_cycle_anchor_time, enabled, updated_at, created_at, id)
default: id
Field to order the plans by.
order_direction
string (one of: asc, desc)
default: desc
Sort direction.
before_id
integer
after_id
integer

Returns

An array of Plan objects.

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 in path required Plan ID.

Returns

A Plan object.

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 required Plan name
billing_amount
number required Amount customer should be billed
setup_fee
number
default: 0
Plan setup fee
setup_fee_type
string (one of: product, service, recurring-fee, one-time-product, one-time-service, fee) The type of setup fee invoice item to add to the order.
setup_fee_sku
string Plan setup fee sku added to the invoice
installments
integer
default: 0
Number of times the customer should be billed. Use 0 for unlimited.
description
string Plan description
billing_interval
integer (min: 1, max: 1000)
default: 1
Interval in which billing should occur
billing_frequency
string (one of: day, week, month, year, dates)
default: month
Note: use 'dates' together with billing_dates to bill on specific calendar dates.
billing_dates
string
example: ["2025-03-15", "2025-09-15"]
When billing_frequency is 'dates', send a JSON-encoded array of Y-m-d dates to bill on, used instead of the billing interval.
trial_days
integer
default: 0
Number of days for a free trial
sku
string Plan SKU
currency
string
default: USD
Currency Code for Plan
require_tax
boolean
default: false
Should the plan have tax rules applied?
require_shipping
boolean
default: false
Should the plan have shipping rules applied?
billing_cycle_anchor
integer (min: 0, max: 31)
default: 0
Day of the month billing should begin. Use 0 for date of signup.
billing_cycle_anchor_time
string
example: 14:30
Time of day for the billing anchor.
enabled
boolean
default: false
Enable to show plan in browse section of customer portal
tags
string Comma-separated list of tags to be added to the plan

Returns

A Plan object.

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 required Plan ID
name
string Plan name
billing_amount
number Amount customer should be billed
setup_fee
number Plan setup fee
setup_fee_type
string (one of: product, service, recurring-fee, one-time-product, one-time-service, fee) The type of setup fee invoice item to add to the order.
setup_fee_sku
string Plan setup fee sku added to the invoice
installments
integer Number of times the customer should be billed. Use 0 for unlimited.
description
string Plan description
billing_interval
integer (min: 1, max: 1000) Interval in which billing should occur
billing_frequency
string (one of: day, week, month, year, dates) Note: use 'dates' together with billing_dates to bill on specific calendar dates.
billing_dates
string
example: ["2025-03-15", "2025-09-15"]
When billing_frequency is 'dates', send a JSON-encoded array of Y-m-d dates to bill on, used instead of the billing interval.
trial_days
integer Number of days for a free trial
sku
string Plan SKU
currency
string
example: USD
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 (min: 0, max: 31) Day of the month billing should begin. Use 0 for date of signup.
billing_cycle_anchor_time
string
example: 14:30
Time of day for the billing anchor.
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

Returns

A Plan object.

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/{customer_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

customer_id
integer in path required
status
string (one of: active, canceled, all)
default: active
Filter by subscription status.

Returns

An array of Subscription objects, each with its plan included.

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_start_iso' => '2024-03-04T11:24:00.000000Z', 'trial_end' => 1710156240, 'trial_end_iso' => '2024-03-11T11:24:00.000000Z', 'current_period_start' => 1710156240, 'current_period_start_iso' => '2024-03-11T11:24:00.000000Z', 'current_period_end' => 1711365840, 'current_period_end_iso' => '2024-03-25T11:24:00.000000Z', '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 in path required Subscription ID.

Returns

A Subscription object.

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_start_iso' => '2024-03-04T11:24:00.000000Z', 'trial_end' => 1710156240, 'trial_end_iso' => '2024-03-11T11:24:00.000000Z', 'current_period_start' => 1710156240, 'current_period_start_iso' => '2024-03-11T11:24:00.000000Z', 'current_period_end' => 1711365840, 'current_period_end_iso' => '2024-03-25T11:24:00.000000Z', '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 required
plan_id
integer required
trial_end
integer Trial end as UNIX timestamp.
promo_id
integer Promo code ID to apply.
quantity
integer (min: 1)
default: 1

Returns

A Subscription object.

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_start_iso' => '2024-03-04T11:24:00.000000Z', 'trial_end' => 1710156240, 'trial_end_iso' => '2024-03-11T11:24:00.000000Z', 'current_period_start' => 1709551440, 'current_period_start_iso' => '2024-03-04T11:24:00.000000Z', 'current_period_end' => 1710156240, 'current_period_end_iso' => '2024-03-11T11:24:00.000000Z', '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 required
plan_id
integer
quantity
integer
address_id
integer Address ID to associate with the subscription invoices.
installments_left
integer Number of installments left (installment plans only).
trial_end
integer UNIX timestamp when the trial expires and billing starts; 0 to start immediately.
card_id
integer Card ID to use as the payment method for this subscription's invoices.

Returns

A Subscription object.

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_start_iso' => '2024-03-04T11:24:00.000000Z', 'trial_end' => 1710761040, 'trial_end_iso' => '2024-03-18T11:24:00.000000Z', 'current_period_start' => 1710156240, 'current_period_start_iso' => '2024-03-11T11:24:00.000000Z', 'current_period_end' => 1710761040, 'current_period_end_iso' => '2024-03-18T11:24:00.000000Z', '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-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 to cancel.
customer_id
integer Cancel all of this customer's subscriptions (use 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 (min: 1, max: 100)
default: 20
Number of results to return.
order_key
string (one of: subscription_id, first_name, last_name, email, customer_id, plan_id, quantity, current_period_end, current_period_start, installment_plan, installments_left, created_at, updated_at)
default: subscription_id
Field to sort by.
order_direction
string (one of: asc, desc)
default: desc
Sort direction.
starting_after
integer Show results with a subscription ID greater than this value.
starting_before
integer Show results with a subscription ID less than this value.
keyword
string Filter by customer first name, last name, or email.

Returns

An array of Subscriber objects.

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', ), ), ), )

POST /subscription/{id}/pause

Pause an active subscription. The customer is notified by email and billing stops until it is unpaused. Calling it on an already-paused subscription is a no-op that returns 200 without notifying the customer again. Returns 422 if the subscription has no upcoming invoice to pause, or if its latest invoice is in a state that cannot be paused (already paid, mid-payment or being processed).

Parameters

id
integer in path required PayWhirl subscription ID.

Returns

A Subscription object.

Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/subscription/29654/pause');
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_start_iso' => '2024-03-04T11:24:00.000000Z', 'trial_end' => 1710156240, 'trial_end_iso' => '2024-03-11T11:24:00.000000Z', 'current_period_start' => 1710156240, 'current_period_start_iso' => '2024-03-11T11:24:00.000000Z', 'current_period_end' => 1711365840, 'current_period_end_iso' => '2024-03-25T11:24:00.000000Z', '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' => '2026-06-30T12:00:00.000000Z', 'deleted_at' => NULL, )

POST /subscription/{id}/unpause

Unpause a paused subscription, resuming billing on its latest invoice on its existing schedule; the customer is notified by email. Calling it on a subscription that isn't paused is a no-op that returns 200 without notifying the customer again. Returns 422 if the subscription has no invoice to unpause, or if its latest invoice is in a state that cannot be edited (already paid, mid-payment or being processed).

Parameters

id
integer in path required PayWhirl subscription ID.

Returns

A Subscription object.

Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/subscription/29654/unpause');
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_start_iso' => '2024-03-04T11:24:00.000000Z', 'trial_end' => 1710156240, 'trial_end_iso' => '2024-03-11T11:24:00.000000Z', 'current_period_start' => 1710156240, 'current_period_start_iso' => '2024-03-11T11:24:00.000000Z', 'current_period_end' => 1711365840, 'current_period_end_iso' => '2024-03-25T11:24:00.000000Z', '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' => '2026-06-30T12:00:00.000000Z', 'deleted_at' => NULL, )

POST /subscription/{id}/reactivate

Reactivate a cancelled subscription so it resumes billing. Works even when the subscription has no invoices left (a never-paid subscription loses its only invoice on cancellation): a fresh upcoming invoice is rebuilt from the plan. Returns 422 if the subscription is not cancelled.

Parameters

id
integer in path required PayWhirl subscription ID.
reactivate_mode
string (one of: one, all)
default: one
How much to bill on reactivation: 'one' = resume from now (no past charges); 'all' = also replay and charge the missed billing periods.
next_payment_attempt_iso
string
example: 2028-01-31T00:00:00+00:00
Optional future ISO 8601 datetime; overrides the next payment date after reactivation.

Returns

A Subscription object.

Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/subscription/29654/reactivate', [ 'json' => [ 'reactivate_mode' => 'one', 'next_payment_attempt_iso' => '2028-01-01T00:00:00+00:00', ] ]);
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_start_iso' => '2024-03-04T11:24:00.000000Z', 'trial_end' => 1710156240, 'trial_end_iso' => '2024-03-11T11:24:00.000000Z', 'current_period_start' => 1710156240, 'current_period_start_iso' => '2024-03-11T11:24:00.000000Z', 'current_period_end' => 1835781840, 'current_period_end_iso' => '2028-03-04T11:24:00.000000Z', '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' => '2026-06-30T12:00:00.000000Z', 'deleted_at' => NULL, )

POST /subscription/{id}/skip-cycle

Skip the next billing cycle of a subscription. The upcoming invoice is marked skipped (the customer is not charged for it) and the following cycle is rescheduled on the existing schedule. Returns 422 if the subscription has no upcoming invoice to skip, if its latest invoice is in a state that cannot be skipped (already paid, mid-payment, being processed, or awaiting confirmation), or if the plan bills on specific dates (which cannot be skipped).

Parameters

id
integer in path required PayWhirl subscription ID.

Returns

A Subscription object.

Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('POST', '/subscription/29654/skip-cycle');
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_start_iso' => '2024-03-04T11:24:00.000000Z', 'trial_end' => 1710156240, 'trial_end_iso' => '2024-03-11T11:24:00.000000Z', 'current_period_start' => 1711365840, 'current_period_start_iso' => '2024-03-25T11:24:00.000000Z', 'current_period_end' => 1712575440, 'current_period_end_iso' => '2024-04-08T11:24:00.000000Z', '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, )

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 in path required Invoice ID.

Returns

An Invoice object.

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' => '', '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' => 1234, 'customer_id' => 54398, '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

List invoices across the account, optionally filtered by customer and/or subscription (useful to inspect a subscription's state and history — pass all=1 to include paid/past invoices). Returns at most 100 invoices per call (default 100); results are ordered by ascending ID, so page forward by passing the last ID as after_id.

Parameters

customer_id
integer Return only invoices for this customer.
subscription_id
integer Return only invoices belonging to this subscription.
tracking_number
string Return only invoices with this shipping tracking number.
all
integer (one of: 0, 1)
default: 0
1 to include past invoices; 0 for upcoming only.
limit
integer (min: 1, max: 100)
default: 10
Maximum number of invoices to return. Page through larger result sets with before_id/after_id.
before_id
integer Return only invoices with ID less than this value (exclusive).
after_id
integer Return only invoices with ID greater than this value (exclusive); use for forward pagination.
created_after_iso
string
example: 2028-01-31T00:00:00+00:00
Return only invoices created strictly after this ISO 8601 datetime (exclusive).
created_before_iso
string
example: 2028-06-30T00:00:00+00:00
Return only invoices created strictly before this ISO 8601 datetime (exclusive).

Returns

An array of Invoice objects.

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' => '', '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, 'big_commerce_order_id' => 12345, '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' => 1234, 'customer_id' => 54398, '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' => '', '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' => 1234, 'customer_id' => 54398, '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 in path required Invoice ID.
customer_present
boolean Set true for on-session SCA flows.
intent_id
string Stripe payment intent ID returned from a prior auth attempt.
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 in path required 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', '/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 in path required Invoice ID.
promo_code
string required Promo code 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 in path required 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', '/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 in path required Invoice ID.
card_id
integer required Card ID belonging to the same customer.
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 line items in a specific invoice.

This method is useful when you want to change the number of line items or amount in an existing invoice. Instead of creating a brand new invoice, you can simply call this method to adjust the line items in the existing invoice.

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's quantities to '0', the item will be deleted from the invoice.

Parameters

id
integer in path required Invoice ID.
<line_item_id>
object Update for the line item whose ID is the property key. Set quantity to 0 to remove the line item.
quantity (integer) — New quantity for the line item. Set to 0 to remove it from the invoice.
amount (number) — New amount for the line item.

Returns

A success status with the number of items updated.

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' => [ 'quantity' => 0, ], '2544' => [ 'quantity' => 1, 'amount' => 99.99, ], ] ]);
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 required
next_payment_attempt
integer UNIX timestamp when the invoice should next process; must be in the future. Required unless paid_on is set, in which case it defaults to 0 (a fully-paid invoice).
paid_on
integer Past UNIX timestamp for a pre-paid invoice.
shipping
integer (one of: 0, 1) Whether to charge shipping on this invoice (1) or not (0). The amount comes from the account's shipping rules, not this flag.
tax
integer (one of: 0, 1) Whether to charge tax on this invoice (1) or not (0). The amount comes from the account's tax rules, not this flag.
currency
string
items
array Array of line items.

Returns

A success status with the new 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', '/invoices', [ 'json' => [ 'customer_id' => 54398, 'next_payment_attempt' => 1709637840, 'shipping' => 1, 'tax' => 1, 'items' => [ '0' => [ '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 required Invoice ID.

Returns

A success status with the deleted invoice.

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' => '', '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 in path required Invoice ID.
next_payment_attempt
integer required New next payment date as UNIX timestamp (future).
all
boolean
default: false
Apply the new date to all future invoices in the subscription. Setting this to true WILL change the customer's billing cycle and future payment dates based on the chosen date.

Returns

An Invoice object.

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' => '', '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' => 1234, 'customer_id' => 54398, '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.

Returns

An array of Gateway objects.

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 in path required Gateway ID.

Returns

A Gateway object.

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
number required
example: 9.99
Charge amount.
quantity
integer (min: 1)
default: 1
Number of units to charge; the amount is multiplied by this.
description
string
schedule
integer UNIX timestamp to schedule the charge (defaults to now).
sku
string

Returns

An Invoice object.

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' => '', '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 in path required Charge ID.

Returns

A Charge object.

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 in path required Charge ID.
refund_amount
number
example: 9.99
Amount to refund (defaults to remaining unrefunded amount).
mark_only
boolean
default: false
If true, marks the charge refunded without contacting the gateway.

Returns

A Charge object.

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 in path required Customer ID.

Returns

An array of Card objects.

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', 'title' => 'Visa (•••• 4242)', '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', 'title' => 'Mastercard (•••• 4242)', '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 in path required Card ID.

Returns

A Card object.

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', 'title' => 'Credit (•••• 4242)', '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

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 should send a paymentMethodId obtained via the Stripe Payment Element / Payment Intents API. This is also the way to save cards that require Strong Customer Authentication (SCA); see Stripe's documentation on saving cards.

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 required Customer ID.
paymentMethodId
string for Stripe: from the Payment Element / createPaymentMethod handler.
payment_method_nonce
string Braintree nonce.
dataDescriptor
string Authorize.net data descriptor.
dataValue
string Authorize.net data value.
token
string Generic third-party gateway token.
number
string Raw card number (test gateway only).
exp_month
string Expiration month MM (test gateway only).
exp_year
string Expiration year YYYY (test gateway only).
first_name
string First name on card (test gateway only).
last_name
string Last name on card (test gateway only).
address
string Billing street address (test gateway only).
city
string Billing city (test gateway only).
state
string Billing state / region (test gateway only).
zip
string Billing zip / postal code (test gateway only).
country
string Billing country (test gateway only).

Returns

A Card object.

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', 'title' => 'Credit (•••• 4242)', '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 required 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.

Returns

An array of Promo objects.

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 single promo code by its ID or code.

Parameters

id
integer in path required Promo code ID.

Returns

A Promo object.

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 required
example: SUMMER20
Uppercase letters and numbers only.
duration
string required (one of: once, repeating, forever)
apply_to
string required (one of: subtotal, grandtotal)
duration_uses
integer Number of billing cycles the discount applies for.
amount_off
number Fixed discount amount.
percent_off
number (min: 0, max: 100) Percentage discount.
max_redemptions
integer Maximum number of times the code can be redeemed before it expires.
redeem_by
integer Expiration UNIX timestamp.
one_use
boolean Customer may redeem only once.
plans
array
example:
Array of plan IDs or upsell titles the promo applies to.

Returns

A Promo object.

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 required Promo code 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.

Returns

An array of Gift Code objects.

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 in path required Email template ID.

Returns

An Email Template object.

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' => '<entire_html_template_here>', '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: Welcome, NewSubscription, CancelledSubscription, NewCard, PaymentReminder, PaymentReceipt, PaymentRequest, ChargeFailed, PasswordReminder, RefundPayment, ProfileUpdate, RedeemGiftCode)
customer_id
integer
invoice_id
integer
subscription_id
integer
card_id
integer
token
string Used by PasswordReminder.
charge_id
integer Used by RefundPayment.
changes
array Used by ProfileUpdate.
gift_code_id
integer Used by RedeemGiftCode.
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.)

Returns

An Account object.

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' => 'starter', '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' => 'smtp', 'host' => 'smtp.example.com', 'username' => '[REDACTED]', 'password' => '[REDACTED]', 'from_address' => '', 'from_name' => '', 'port' => '587', ), '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.)

Returns

A Stats object.

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' => 'starter', '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.

Returns

An array of Shipping objects.

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 in path required Shipping rule ID.

Returns

A Shipping object.

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.

Returns

An array of Tax objects.

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 in path required Tax rule ID.

Returns

A Tax object.

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', )

Widgets

Widgets are the embeddable checkout components you place on your own site so customers can subscribe to your plans or buy your products.

Each widget carries the ready-to-paste embed snippets (a full widget and a buy button) along with the gateway it collects payments through. Widgets are created and configured in the PayWhirl dashboard.

GET /widgets

Get a list of checkout widgets associated with your account.

Returns

An array of Widget objects.

Sample request:
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://api.paywhirl.com', 'headers' => ['api-key' => $apiKey, 'api-secret' => $apiSecret], ]); $response = $client->request('GET', '/widgets');
Sample response:
var_export(json_decode($response->getBody())); array ( 0 => array ( 'uuid' => '56fc27ae-b718-4956-abe4-41f1f539ca77', 'name' => 'subscription', 'type' => 'table', 'widget_snippet' => '<script type="text/javascript" id=\'pw_mocked_id_1\' src="https://app.localhost/pwa.js"></script> <script> paywhirl(\'widget\', { autoscroll: 1, initial_autoscroll: 0, domain: \'test-store\', uuid: \'56fc27ae-b718-4956-abe4-41f1f539ca77\' }, \'pw_mocked_id_1\'); </script>', 'buy_button_snippet' => '<script type="text/javascript" id=\'pw_mocked_id_2\' src="https://app.localhost/pwa.js"></script> <script> paywhirl(\'button\', { autoscroll: 1, domain: \'test-store\', uuid: \'56fc27ae-b718-4956-abe4-41f1f539ca77\', button_image: \'\' }, \'pw_mocked_id_2\'); </script>', 'gateway_id' => 0, 'user_id' => 1234, 'created_at' => '2023-05-15T10:03:49.000000Z', 'updated_at' => '2023-05-15T10:04:06.000000Z', ), 1 => array ( 'uuid' => '56fc27ae-b718-4956-abe4-41f1f539ca77', 'name' => 'subscription', 'type' => 'table', 'widget_snippet' => '<script type="text/javascript" id=\'pw_mocked_id_3\' src="https://app.localhost/pwa.js"></script> <script> paywhirl(\'widget\', { autoscroll: 1, initial_autoscroll: 0, domain: \'test-store\', uuid: \'56fc27ae-b718-4956-abe4-41f1f539ca77\' }, \'pw_mocked_id_3\'); </script>', 'buy_button_snippet' => '<script type="text/javascript" id=\'pw_mocked_id_4\' src="https://app.localhost/pwa.js"></script> <script> paywhirl(\'button\', { autoscroll: 1, domain: \'test-store\', uuid: \'56fc27ae-b718-4956-abe4-41f1f539ca77\', button_image: \'\' }, \'pw_mocked_id_4\'); </script>', 'gateway_id' => 0, 'user_id' => 1234, 'created_at' => '2023-05-15T10:03:49.000000Z', 'updated_at' => '2023-05-15T10:04:06.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.

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>

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.

POST /multiauth

Get a MultiAuth token to use to automatically login a customer to a widget. When you use this 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.

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.

Returns

A MultiAuth token plus the resolved customer's email and id.

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

Example:

{
    "type": "customer.created",
    "customer": {
        "id": 1234,
        "user_id": 12,
        "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": "6802421d87ed8",
        "default_card": 0,
        "gateway_type": "PaywhirlTest",
        "gateway_id": 123,
        "currency": "USD",
        "utm_source": "Google",
        "utm_medium": "cpc",
        "utm_term": "online payments",
        "utm_content": "logolink",
        "utm_campaign": "spring_sale",
        "utm_group": "users",
        "metadata": "{\"origin\":\"api\"}",
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    },
    "answers": []
}


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

Example:

{
    "type": "customer.updated",
    "customer": {
        "id": 1234,
        "user_id": 12,
        "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": "6802421d87ed8",
        "default_card": 0,
        "gateway_type": "PaywhirlTest",
        "gateway_id": 123,
        "currency": "USD",
        "utm_source": "Google",
        "utm_medium": "cpc",
        "utm_term": "online payments",
        "utm_content": "logolink",
        "utm_campaign": "spring_sale",
        "utm_group": "users",
        "metadata": "{\"origin\":\"api\"}",
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    },
    "answers": [
        {
            "label": "Ground or Whole Bean?",
            "answer": "Whole Bean"
        }
    ]
}



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

Example:

{
    "type": "plan.created",
    "plan": {
        "id": 1234,
        "user_id": 123,
        "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": 0,
        "installments": 0,
        "require_shipping": 1,
        "cancellation_type": null,
        "autorenew_setup_fee": 0,
        "autorenew_plan": 0,
        "email_settings": null,
        "send_reminders": null,
        "questions": "[{\"name\":\"checkbox-1513638794611\",\"repeated\":1}]",
        "display_download": "",
        "file": "",
        "tags": "",
        "metadata": "",
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    }
}


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

Example:

{
    "type": "plan.updated",
    "plan": {
        "id": 1234,
        "user_id": 123,
        "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": 0,
        "installments": 0,
        "require_shipping": 1,
        "cancellation_type": null,
        "autorenew_setup_fee": 0,
        "autorenew_plan": 0,
        "email_settings": null,
        "send_reminders": null,
        "questions": "[{\"name\":\"checkbox-1513638794611\",\"repeated\":1}]",
        "display_download": "",
        "file": "",
        "tags": "",
        "metadata": "",
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    }
}


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

Example:

{
    "type": "card.created",
    "customer": {
        "id": 1234,
        "user_id": 123,
        "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": 0,
        "gateway_type": "PaywhirlTest",
        "gateway_id": 12345,
        "currency": "USD",
        "utm_source": "",
        "utm_medium": "",
        "utm_term": "",
        "utm_content": "",
        "utm_campaign": "",
        "utm_group": "",
        "metadata": "{\"origin\":\"api\"}",
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    },
    "answers": [],
    "card": {
        "id": 123456,
        "user_id": 123,
        "customer_id": 1234,
        "gateway_id": 12345,
        "gateway_reference": "fake-ref",
        "brand": "Visa",
        "title": "Visa (\u2022\u2022\u2022\u2022 4242)",
        "funding": "Credit",
        "country": "US",
        "last4": "4242",
        "exp_date": "12\/2032",
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z"
    }
}



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

Example:

{
    "type": "card.deleted",
    "customer": {
        "id": 1234,
        "user_id": 123,
        "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": 0,
        "gateway_type": "PaywhirlTest",
        "gateway_id": 12345,
        "currency": "USD",
        "utm_source": "",
        "utm_medium": "",
        "utm_term": "",
        "utm_content": "",
        "utm_campaign": "",
        "utm_group": "",
        "metadata": "{\"origin\":\"api\"}",
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    },
    "answers": [],
    "card": {
        "id": 123456,
        "user_id": 123,
        "customer_id": 1234,
        "gateway_id": 12345,
        "gateway_reference": "fake-ref",
        "brand": "Visa",
        "title": "Visa (\u2022\u2022\u2022\u2022 4242)",
        "funding": "Credit",
        "country": "US",
        "last4": "4242",
        "exp_date": "12\/2032",
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z"
    }
}



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

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

Example:

{
    "type": "subscription.created",
    "customer": {
        "id": 1234,
        "user_id": 123,
        "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": 0,
        "gateway_type": "PaywhirlTest",
        "gateway_id": 0,
        "currency": "USD",
        "utm_source": "",
        "utm_medium": "",
        "utm_term": "",
        "utm_content": "",
        "utm_campaign": "",
        "utm_group": "",
        "metadata": "{\"origin\":\"api\"}",
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    },
    "answers": [
        {
            "label": "Ground or Whole Bean?",
            "answer": "Whole Bean"
        }
    ],
    "subscription": {
        "id": 1337,
        "user_id": 123,
        "customer_id": 1234,
        "plan_id": 122,
        "widget_id": 0,
        "quantity": 1,
        "trial_start": 0,
        "trial_end": 0,
        "current_period_start": 1739791440,
        "current_period_end": 1742383440,
        "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": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    },
    "invoice": {
        "id": 461175,
        "user_id": 123,
        "customer_id": 1234,
        "subscription_id": 1337,
        "charge_id": 0,
        "additional_charge_id": null,
        "card_id": 0,
        "address_id": 0,
        "amount_due": 198.98,
        "currency": "USD",
        "discount_id": 0,
        "discount": "0.00",
        "shipping": 0,
        "shipping_total": 0,
        "tax": 0,
        "status": "Subscribed",
        "paid": 0,
        "subtotal": 198.98,
        "attempted": 0,
        "attempt_count": 0,
        "due_date": 0,
        "next_payment_attempt": 1742383440,
        "period_end": 1742383440,
        "period_start": 1739791440,
        "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": "",
        "shopify_created_at": null,
        "metadata": null,
        "tax_integration_id": null,
        "tax_calculated_at": null,
        "tax_status": null,
        "tax_date": null,
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    },
    "order_address": {
        "first_name": "Awesome",
        "last_name": "Person",
        "address": "123, Easy Street",
        "city": "Los Angeles",
        "state": "California",
        "zip": "93003",
        "country": "US",
        "phone": "4564564566"
    }
}



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

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

Example:

{
    "type": "subscription.updated",
    "customer": {
        "id": 1234,
        "user_id": 123,
        "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": 0,
        "gateway_type": "PaywhirlTest",
        "gateway_id": 0,
        "currency": "USD",
        "utm_source": "",
        "utm_medium": "",
        "utm_term": "",
        "utm_content": "",
        "utm_campaign": "",
        "utm_group": "",
        "metadata": "{\"origin\":\"api\"}",
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    },
    "answers": [
        {
            "label": "Ground or Whole Bean?",
            "answer": "Whole Bean"
        }
    ],
    "subscription": {
        "id": 1337,
        "user_id": 123,
        "customer_id": 1234,
        "plan_id": 122,
        "widget_id": 0,
        "quantity": 1,
        "trial_start": 0,
        "trial_end": 0,
        "current_period_start": 1739791440,
        "current_period_end": 1742383440,
        "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": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    },
    "invoice": {
        "id": 461175,
        "user_id": 123,
        "customer_id": 1234,
        "subscription_id": 1337,
        "charge_id": 0,
        "additional_charge_id": null,
        "card_id": 0,
        "address_id": 0,
        "amount_due": 198.98,
        "currency": "USD",
        "discount_id": 0,
        "discount": "0.00",
        "shipping": 0,
        "shipping_total": 0,
        "tax": 0,
        "status": "Subscribed",
        "paid": 0,
        "subtotal": 198.98,
        "attempted": 0,
        "attempt_count": 0,
        "due_date": 0,
        "next_payment_attempt": 1742383440,
        "period_end": 1742383440,
        "period_start": 1739791440,
        "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": "",
        "shopify_created_at": null,
        "metadata": null,
        "tax_integration_id": null,
        "tax_calculated_at": null,
        "tax_status": null,
        "tax_date": null,
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    },
    "order_address": {
        "first_name": "Awesome",
        "last_name": "Person",
        "address": "123, Easy Street",
        "city": "Los Angeles",
        "state": "California",
        "zip": "93003",
        "country": "US",
        "phone": "4564564566"
    }
}


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

Subscription Deleted

Example:

{
    "type": "subscription.deleted",
    "customer": {
        "id": 1234,
        "user_id": 123,
        "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": 0,
        "gateway_type": "PaywhirlTest",
        "gateway_id": 0,
        "currency": "USD",
        "utm_source": "",
        "utm_medium": "",
        "utm_term": "",
        "utm_content": "",
        "utm_campaign": "",
        "utm_group": "",
        "metadata": "{\"origin\":\"api\"}",
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    },
    "answers": [
        {
            "label": "Ground or Whole Bean?",
            "answer": "Whole Bean"
        }
    ],
    "subscription": {
        "id": 1337,
        "user_id": 123,
        "customer_id": 1234,
        "plan_id": 122,
        "widget_id": 0,
        "quantity": 1,
        "trial_start": 0,
        "trial_end": 0,
        "current_period_start": 1739791440,
        "current_period_end": 1742383440,
        "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": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    },
    "invoice": {
        "id": 461175,
        "user_id": 123,
        "customer_id": 1234,
        "subscription_id": 1337,
        "charge_id": 0,
        "additional_charge_id": null,
        "card_id": 0,
        "address_id": 0,
        "amount_due": 198.98,
        "currency": "USD",
        "discount_id": 0,
        "discount": "0.00",
        "shipping": 0,
        "shipping_total": 0,
        "tax": 0,
        "status": "Subscribed",
        "paid": 0,
        "subtotal": 198.98,
        "attempted": 0,
        "attempt_count": 0,
        "due_date": 0,
        "next_payment_attempt": 1742383440,
        "period_end": 1742383440,
        "period_start": 1739791440,
        "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": "",
        "shopify_created_at": null,
        "metadata": null,
        "tax_integration_id": null,
        "tax_calculated_at": null,
        "tax_status": null,
        "tax_date": null,
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    },
    "order_address": {
        "first_name": "Awesome",
        "last_name": "Person",
        "address": "123, Easy Street",
        "city": "Los Angeles",
        "state": "California",
        "zip": "93003",
        "country": "US",
        "phone": "4564564566"
    }
}


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

Example:

{
    "type": "invoice.created",
    "customer": {
        "id": 1234,
        "user_id": 123,
        "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": 0,
        "gateway_type": "PaywhirlTest",
        "gateway_id": 0,
        "currency": "USD",
        "utm_source": "",
        "utm_medium": "",
        "utm_term": "",
        "utm_content": "",
        "utm_campaign": "",
        "utm_group": "",
        "metadata": "{\"origin\":\"api\"}",
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    },
    "answers": [
        {
            "label": "Ground or Whole Bean?",
            "answer": "Whole Bean"
        }
    ],
    "invoice": {
        "id": 461175,
        "user_id": 123,
        "customer_id": 1234,
        "subscription_id": 1337,
        "charge_id": 0,
        "additional_charge_id": null,
        "card_id": 0,
        "address_id": 0,
        "amount_due": 198.98,
        "currency": "USD",
        "discount_id": 0,
        "discount": "0.00",
        "shipping": 0,
        "shipping_total": 0,
        "tax": 0,
        "status": "Subscribed",
        "paid": 0,
        "subtotal": 198.98,
        "attempted": 0,
        "attempt_count": 0,
        "due_date": 0,
        "next_payment_attempt": 1742383440,
        "period_end": 1742383440,
        "period_start": 1739791440,
        "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": "",
        "shopify_created_at": null,
        "metadata": null,
        "tax_integration_id": null,
        "tax_calculated_at": null,
        "tax_status": null,
        "tax_date": null,
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null,
        "items": [
            {
                "id": 123456,
                "type": "plan",
                "user_id": 123,
                "customer_id": 1234,
                "invoice_id": 461175,
                "plan_id": null,
                "quantity": 2,
                "description": "Subscription to Coffee Daily 1",
                "amount": 2,
                "currency": "USD",
                "image": null,
                "tax_code": null,
                "sku": "",
                "vendor_product_id": null,
                "upsell_index": null,
                "created_at": "2025-03-04T11:24:00.000000Z",
                "updated_at": "2025-03-04T11:24:00.000000Z",
                "deleted_at": null
            }
        ]
    },
    "order_address": {
        "first_name": "Awesome",
        "last_name": "Person",
        "address": "123, Easy Street",
        "city": "Los Angeles",
        "state": "California",
        "zip": "93003",
        "country": "US",
        "phone": "4564564566"
    }
}


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

Example:

{
    "type": "invoice.processed",
    "customer": {
        "id": 1234,
        "user_id": 123,
        "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": 0,
        "gateway_type": "PaywhirlTest",
        "gateway_id": 0,
        "currency": "USD",
        "utm_source": "",
        "utm_medium": "",
        "utm_term": "",
        "utm_content": "",
        "utm_campaign": "",
        "utm_group": "",
        "metadata": "{\"origin\":\"api\"}",
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    },
    "answers": [
        {
            "label": "Ground or Whole Bean?",
            "answer": "Whole Bean"
        }
    ],
    "invoice": {
        "id": 461175,
        "user_id": 123,
        "customer_id": 1234,
        "subscription_id": 1337,
        "charge_id": 0,
        "additional_charge_id": null,
        "card_id": 0,
        "address_id": 0,
        "amount_due": 198.98,
        "currency": "USD",
        "discount_id": 0,
        "discount": "0.00",
        "shipping": 0,
        "shipping_total": 0,
        "tax": 0,
        "status": "Paid",
        "paid": 1,
        "subtotal": 198.98,
        "attempted": 0,
        "attempt_count": 0,
        "due_date": 0,
        "next_payment_attempt": 0,
        "period_end": 1742383440,
        "period_start": 1739791440,
        "tax_total": 0,
        "tax_rule_name": null,
        "refunded_amount": 0,
        "webhooks_delivered_at": 0,
        "skipped_at": null,
        "paid_on": 1741087440,
        "promo_id": 0,
        "promo_code": "",
        "promo_uses": 0,
        "integration_errors": "",
        "carrier": "",
        "service": "",
        "tracking_number": "",
        "shopify_created_at": null,
        "metadata": null,
        "tax_integration_id": null,
        "tax_calculated_at": null,
        "tax_status": null,
        "tax_date": null,
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null,
        "items": [
            {
                "id": 123456,
                "type": "plan",
                "user_id": 123,
                "customer_id": 1234,
                "invoice_id": 461175,
                "plan_id": null,
                "quantity": 2,
                "description": "Subscription to Coffee Daily 1",
                "amount": 2,
                "currency": "USD",
                "image": null,
                "tax_code": null,
                "sku": "",
                "vendor_product_id": null,
                "upsell_index": null,
                "created_at": "2025-03-04T11:24:00.000000Z",
                "updated_at": "2025-03-04T11:24:00.000000Z",
                "deleted_at": null
            }
        ]
    },
    "order_address": {
        "first_name": "Awesome",
        "last_name": "Person",
        "address": "123, Easy Street",
        "city": "Los Angeles",
        "state": "California",
        "zip": "93003",
        "country": "US",
        "phone": "4564564566"
    }
}


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

Example:

{
    "type": "invoice.paid",
    "customer": {
        "id": 1234,
        "user_id": 123,
        "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": 0,
        "gateway_type": "PaywhirlTest",
        "gateway_id": 0,
        "currency": "USD",
        "utm_source": "",
        "utm_medium": "",
        "utm_term": "",
        "utm_content": "",
        "utm_campaign": "",
        "utm_group": "",
        "metadata": "{\"origin\":\"api\"}",
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    },
    "answers": [
        {
            "label": "Ground or Whole Bean?",
            "answer": "Whole Bean"
        }
    ],
    "invoice": {
        "id": 461175,
        "user_id": 123,
        "customer_id": 1234,
        "subscription_id": 1337,
        "charge_id": 0,
        "additional_charge_id": null,
        "card_id": 0,
        "address_id": 0,
        "amount_due": 198.98,
        "currency": "USD",
        "discount_id": 0,
        "discount": "0.00",
        "shipping": 0,
        "shipping_total": 0,
        "tax": 0,
        "status": "Paid",
        "paid": 1,
        "subtotal": 198.98,
        "attempted": 0,
        "attempt_count": 0,
        "due_date": 0,
        "next_payment_attempt": 0,
        "period_end": 1742383440,
        "period_start": 1739791440,
        "tax_total": 0,
        "tax_rule_name": null,
        "refunded_amount": 0,
        "webhooks_delivered_at": 0,
        "skipped_at": null,
        "paid_on": 1741087440,
        "promo_id": 0,
        "promo_code": "",
        "promo_uses": 0,
        "integration_errors": "",
        "carrier": "",
        "service": "",
        "tracking_number": "",
        "shopify_created_at": null,
        "metadata": null,
        "tax_integration_id": null,
        "tax_calculated_at": null,
        "tax_status": null,
        "tax_date": null,
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null,
        "items": [
            {
                "id": 123456,
                "type": "plan",
                "user_id": 123,
                "customer_id": 1234,
                "invoice_id": 461175,
                "plan_id": null,
                "quantity": 2,
                "description": "Subscription to Coffee Daily 1",
                "amount": 2,
                "currency": "USD",
                "image": null,
                "tax_code": null,
                "sku": "",
                "vendor_product_id": null,
                "upsell_index": null,
                "created_at": "2025-03-04T11:24:00.000000Z",
                "updated_at": "2025-03-04T11:24:00.000000Z",
                "deleted_at": null
            }
        ]
    },
    "order_address": {
        "first_name": "Awesome",
        "last_name": "Person",
        "address": "123, Easy Street",
        "city": "Los Angeles",
        "state": "California",
        "zip": "93003",
        "country": "US",
        "phone": "4564564566"
    }
}


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 Deleted

Example:

{
    "type": "invoice.deleted",
    "customer": {
        "id": 1234,
        "user_id": 123,
        "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": 0,
        "gateway_type": "PaywhirlTest",
        "gateway_id": 0,
        "currency": "USD",
        "utm_source": "",
        "utm_medium": "",
        "utm_term": "",
        "utm_content": "",
        "utm_campaign": "",
        "utm_group": "",
        "metadata": "{\"origin\":\"api\"}",
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    },
    "answers": [
        {
            "label": "Ground or Whole Bean?",
            "answer": "Whole Bean"
        }
    ],
    "invoice": {
        "id": 461175,
        "user_id": 123,
        "customer_id": 1234,
        "subscription_id": 1337,
        "charge_id": 0,
        "additional_charge_id": null,
        "card_id": 0,
        "address_id": 0,
        "amount_due": 198.98,
        "currency": "USD",
        "discount_id": 0,
        "discount": "0.00",
        "shipping": 0,
        "shipping_total": 0,
        "tax": 0,
        "status": "Paid",
        "paid": 1,
        "subtotal": 198.98,
        "attempted": 0,
        "attempt_count": 0,
        "due_date": 0,
        "next_payment_attempt": 0,
        "period_end": 1742383440,
        "period_start": 1739791440,
        "tax_total": 0,
        "tax_rule_name": null,
        "refunded_amount": 0,
        "webhooks_delivered_at": 0,
        "skipped_at": null,
        "paid_on": 1741087440,
        "promo_id": 0,
        "promo_code": "",
        "promo_uses": 0,
        "integration_errors": "",
        "carrier": "",
        "service": "",
        "tracking_number": "",
        "shopify_created_at": null,
        "metadata": null,
        "tax_integration_id": null,
        "tax_calculated_at": null,
        "tax_status": null,
        "tax_date": null,
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null,
        "items": [
            {
                "id": 123456,
                "type": "plan",
                "user_id": 123,
                "customer_id": 1234,
                "invoice_id": 461175,
                "plan_id": null,
                "quantity": 2,
                "description": "Subscription to Coffee Daily 1",
                "amount": 2,
                "currency": "USD",
                "image": null,
                "tax_code": null,
                "sku": "",
                "vendor_product_id": null,
                "upsell_index": null,
                "created_at": "2025-03-04T11:24:00.000000Z",
                "updated_at": "2025-03-04T11:24:00.000000Z",
                "deleted_at": null
            }
        ]
    },
    "order_address": {
        "first_name": "Awesome",
        "last_name": "Person",
        "address": "123, Easy Street",
        "city": "Los Angeles",
        "state": "California",
        "zip": "93003",
        "country": "US",
        "phone": "4564564566"
    }
}


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

Example:

{
    "type": "charge.succeeded",
    "customer": {
        "id": 1234,
        "user_id": 123,
        "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": 0,
        "gateway_type": "PaywhirlTest",
        "gateway_id": 0,
        "currency": "USD",
        "utm_source": "",
        "utm_medium": "",
        "utm_term": "",
        "utm_content": "",
        "utm_campaign": "",
        "utm_group": "",
        "metadata": "{\"origin\":\"api\"}",
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    },
    "charge": {
        "id": 12345678,
        "user_id": 123,
        "customer_id": 1234,
        "gateway_id": 0,
        "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": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    },
    "invoice": {
        "id": 461175,
        "user_id": 123,
        "customer_id": 1234,
        "subscription_id": 1337,
        "charge_id": 12345678,
        "additional_charge_id": null,
        "card_id": 0,
        "address_id": 0,
        "amount_due": 198.98,
        "currency": "USD",
        "discount_id": 0,
        "discount": "0.00",
        "shipping": 0,
        "shipping_total": 0,
        "tax": 0,
        "status": "Paid",
        "paid": 1,
        "subtotal": 198.98,
        "attempted": 0,
        "attempt_count": 0,
        "due_date": 0,
        "next_payment_attempt": 0,
        "period_end": 1742383440,
        "period_start": 1739791440,
        "tax_total": 0,
        "tax_rule_name": null,
        "refunded_amount": 0,
        "webhooks_delivered_at": 0,
        "skipped_at": null,
        "paid_on": 1741087440,
        "promo_id": 0,
        "promo_code": "",
        "promo_uses": 0,
        "integration_errors": "",
        "carrier": "",
        "service": "",
        "tracking_number": "",
        "shopify_created_at": null,
        "metadata": null,
        "tax_integration_id": null,
        "tax_calculated_at": null,
        "tax_status": null,
        "tax_date": null,
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null,
        "items": [
            {
                "id": 123456,
                "type": "plan",
                "user_id": 123,
                "customer_id": 1234,
                "invoice_id": 461175,
                "plan_id": null,
                "quantity": 2,
                "description": "Subscription to Coffee Daily 1",
                "amount": 2,
                "currency": "USD",
                "image": null,
                "tax_code": null,
                "sku": "",
                "vendor_product_id": null,
                "upsell_index": null,
                "created_at": "2025-03-04T11:24:00.000000Z",
                "updated_at": "2025-03-04T11:24:00.000000Z",
                "deleted_at": null
            }
        ]
    },
    "order_address": {
        "first_name": "Awesome",
        "last_name": "Person",
        "address": "123, Easy Street",
        "city": "Los Angeles",
        "state": "California",
        "zip": "93003",
        "country": "US",
        "phone": "4564564566"
    },
    "answers": [
        {
            "label": "Ground or Whole Bean?",
            "answer": "Whole Bean"
        }
    ]
}


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

Example:

{
    "type": "charge.failed",
    "customer": {
        "id": 1234,
        "user_id": 123,
        "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": 0,
        "gateway_type": "PaywhirlTest",
        "gateway_id": 0,
        "currency": "USD",
        "utm_source": "",
        "utm_medium": "",
        "utm_term": "",
        "utm_content": "",
        "utm_campaign": "",
        "utm_group": "",
        "metadata": "{\"origin\":\"api\"}",
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    },
    "answers": [
        {
            "label": "Ground or Whole Bean?",
            "answer": "Whole Bean"
        }
    ],
    "invoice": {
        "id": 461175,
        "user_id": 123,
        "customer_id": 1234,
        "subscription_id": 1337,
        "charge_id": 0,
        "additional_charge_id": null,
        "card_id": 0,
        "address_id": 0,
        "amount_due": 198.98,
        "currency": "USD",
        "discount_id": 0,
        "discount": "0.00",
        "shipping": 0,
        "shipping_total": 0,
        "tax": 0,
        "status": "Failed - Cancelled",
        "paid": 0,
        "subtotal": 198.98,
        "attempted": 0,
        "attempt_count": 0,
        "due_date": 0,
        "next_payment_attempt": 0,
        "period_end": 1742383440,
        "period_start": 1739791440,
        "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": "",
        "shopify_created_at": null,
        "metadata": null,
        "tax_integration_id": null,
        "tax_calculated_at": null,
        "tax_status": null,
        "tax_date": null,
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null,
        "items": [
            {
                "id": 123456,
                "type": "plan",
                "user_id": 123,
                "customer_id": 1234,
                "invoice_id": 461175,
                "plan_id": null,
                "quantity": 2,
                "description": "Subscription to Coffee Daily 1",
                "amount": 2,
                "currency": "USD",
                "image": null,
                "tax_code": null,
                "sku": "",
                "vendor_product_id": null,
                "upsell_index": null,
                "created_at": "2025-03-04T11:24:00.000000Z",
                "updated_at": "2025-03-04T11:24:00.000000Z",
                "deleted_at": null
            }
        ]
    },
    "order_address": {
        "first_name": "Awesome",
        "last_name": "Person",
        "address": "123, Easy Street",
        "city": "Los Angeles",
        "state": "California",
        "zip": "93003",
        "country": "US",
        "phone": "4564564566"
    },
    "subscription": {
        "id": 1337,
        "user_id": 123,
        "customer_id": 1234,
        "plan_id": 122,
        "widget_id": 0,
        "quantity": 1,
        "trial_start": 0,
        "trial_end": 0,
        "current_period_start": 1739791440,
        "current_period_end": 1742383440,
        "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": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    },
    "plan": {
        "id": 1234,
        "user_id": 123,
        "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": 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": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    }
}


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

Example:

{
    "type": "charge.refunded",
    "customer": {
        "id": 1234,
        "user_id": 123,
        "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": 0,
        "gateway_type": "PaywhirlTest",
        "gateway_id": 0,
        "currency": "USD",
        "utm_source": "",
        "utm_medium": "",
        "utm_term": "",
        "utm_content": "",
        "utm_campaign": "",
        "utm_group": "",
        "metadata": "{\"origin\":\"api\"}",
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    },
    "charge": {
        "id": 12345678,
        "user_id": 123,
        "customer_id": 1234,
        "gateway_id": 0,
        "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": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    },
    "invoice": {
        "id": 461175,
        "user_id": 123,
        "customer_id": 1234,
        "subscription_id": 1337,
        "charge_id": 12345678,
        "additional_charge_id": null,
        "card_id": 0,
        "address_id": 0,
        "amount_due": 198.98,
        "currency": "USD",
        "discount_id": 0,
        "discount": "0.00",
        "shipping": 0,
        "shipping_total": 0,
        "tax": 0,
        "status": "Paid",
        "paid": 1,
        "subtotal": 198.98,
        "attempted": 0,
        "attempt_count": 0,
        "due_date": 0,
        "next_payment_attempt": 0,
        "period_end": 1742383440,
        "period_start": 1739791440,
        "tax_total": 0,
        "tax_rule_name": null,
        "refunded_amount": 0,
        "webhooks_delivered_at": 0,
        "skipped_at": null,
        "paid_on": 1741087440,
        "promo_id": 0,
        "promo_code": "",
        "promo_uses": 0,
        "integration_errors": "",
        "carrier": "",
        "service": "",
        "tracking_number": "",
        "shopify_created_at": null,
        "metadata": null,
        "tax_integration_id": null,
        "tax_calculated_at": null,
        "tax_status": null,
        "tax_date": null,
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null,
        "items": [
            {
                "id": 123456,
                "type": "plan",
                "user_id": 123,
                "customer_id": 1234,
                "invoice_id": 461175,
                "plan_id": null,
                "quantity": 2,
                "description": "Subscription to Coffee Daily 1",
                "amount": 2,
                "currency": "USD",
                "image": null,
                "tax_code": null,
                "sku": "",
                "vendor_product_id": null,
                "upsell_index": null,
                "created_at": "2025-03-04T11:24:00.000000Z",
                "updated_at": "2025-03-04T11:24:00.000000Z",
                "deleted_at": null
            }
        ]
    },
    "order_address": {
        "first_name": "Awesome",
        "last_name": "Person",
        "address": "123, Easy Street",
        "city": "Los Angeles",
        "state": "California",
        "zip": "93003",
        "country": "US",
        "phone": "4564564566"
    },
    "answers": [
        {
            "label": "Ground or Whole Bean?",
            "answer": "Whole Bean"
        }
    ]
}


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

Example:

{
    "type": "giftcode.sent",
    "customer": {
        "id": 1234,
        "user_id": 123,
        "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": 0,
        "gateway_type": "PaywhirlTest",
        "gateway_id": 0,
        "currency": "USD",
        "utm_source": "",
        "utm_medium": "",
        "utm_term": "",
        "utm_content": "",
        "utm_campaign": "",
        "utm_group": "",
        "metadata": "{\"origin\":\"api\"}",
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null
    },
    "giftcode": {
        "id": 43254,
        "user_id": 123,
        "customer_id": 1234,
        "invoice_item_id": 123456,
        "redeemed_by": null,
        "redeemed_at": null,
        "code": "GIFT-1234",
        "amount": 4,
        "remaining": 4,
        "currency": "USD",
        "gift_message": "A wonderful gift awaits!",
        "recipient_email": "recipient@example.com",
        "email_template": null,
        "redeem_url": null,
        "send_on": null,
        "sent_at": null,
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z"
    },
    "invoice": {
        "id": 461175,
        "user_id": 123,
        "customer_id": 1234,
        "subscription_id": 1337,
        "charge_id": 0,
        "additional_charge_id": null,
        "card_id": 0,
        "address_id": 0,
        "amount_due": 198.98,
        "currency": "USD",
        "discount_id": 0,
        "discount": "0.00",
        "shipping": 0,
        "shipping_total": 0,
        "tax": 0,
        "status": "Subscribed",
        "paid": 0,
        "subtotal": 198.98,
        "attempted": 0,
        "attempt_count": 0,
        "due_date": 0,
        "next_payment_attempt": 1742383440,
        "period_end": 1742383440,
        "period_start": 1739791440,
        "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": "",
        "shopify_created_at": null,
        "metadata": null,
        "tax_integration_id": null,
        "tax_calculated_at": null,
        "tax_status": null,
        "tax_date": null,
        "created_at": "2025-03-04T11:24:00.000000Z",
        "updated_at": "2025-03-04T11:24:00.000000Z",
        "deleted_at": null,
        "items": [
            {
                "id": 123456,
                "type": "gift",
                "user_id": 123,
                "customer_id": 1234,
                "invoice_id": 461175,
                "plan_id": null,
                "quantity": 1,
                "description": "Gift code",
                "amount": 4,
                "currency": "USD",
                "image": null,
                "tax_code": null,
                "sku": "",
                "vendor_product_id": null,
                "upsell_index": null,
                "created_at": "2025-03-04T11:24:00.000000Z",
                "updated_at": "2025-03-04T11:24:00.000000Z",
                "deleted_at": null
            }
        ]
    },
    "order_address": {
        "first_name": "Awesome",
        "last_name": "Person",
        "address": "123, Easy Street",
        "city": "Los Angeles",
        "state": "California",
        "zip": "93003",
        "country": "US",
        "phone": "4564564566"
    },
    "answers": [
        {
            "label": "Ground or Whole Bean?",
            "answer": "Whole Bean"
        }
    ]
}


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
}