API Implementation
Gateway API Endpoints
To build your integration, you will utilize the Orders
endpoints listed below. Later sections cover how to receive requests and the format of the webhooks that you will use to get updates about Zip orders initiated through the gateway API.
Endpoints and Environments
You will be given unique credentials for the sandbox and production environments. It is best to build your integration against the sandbox environment and enable in production upon successful certification.
Production - US - https://gateway.us.zip.co/
Sandbox (Test) - US - https://gateway.sand.us.zip.co/
Sent from the user's browser via a form POST, will start a checkout session within Zip. It will automatically redirect the user to our Zip checkout. You need to ensure you provide your Merchant ID, Merchant Reference (the unique ID you use for the order in your system), confirm/cancel URLs, and the order amount. The order amount should include all shipping, tax, and discount charges. You do not need to provide the order ID as that will be generated for you. Passing as much customer information as possible will improve the user experience by pre-filling data fields and improving conversion.
Required fields are:
- merchantId - Your identifier provided by Zip
- merchantReference - Your unique order identifier for this order in your system
- callbackUrl - A URL that will receive the webhook request about the result of this operation
- amount - The total amount of the order, including cart value, shipping, tax, and all fees
- redirectCancelUrl - Where the user should go upon abandoning their cart
- redirectConfirmUrl - Where the user should go upon order completion (will include the aforementioned query parameters)
Example
Request URL: https://gateway.us.zip.co/orders/authorize
POST Body:
{
"merchantId": "Insert merchant Id",
"merchantReference": "ref",
"order": {
"currency": "USD",
"amount": 123.45,
},
"capture": true,
"redirectCancelUrl": "https://www.bing.com",
"redirectConfirmUrl": "https://www.google.com",
"callbackUrl": "https://api.merchant.com"
}
Use the
test
parameter and set it to"true"
if you are using our bypass OTP in sandbox feature.
Same as the above endpoint but data is passed as query string parameters with a GET
request.
Example
Request URL: https://gateway.us.zip.co/orders/authorize?merchantId=[Insert merchant Id]&merchantReference=TH-20200707-1da6&order.firstName=Aaron&order.lastName=Smith&order.email=869765666.test%40quadpay.com&order.phone=5555555555&order.billingAddress.line1=123+Main+St&order.billingAddress.city=New+York&order.billingAddress.state=NY&order.billingAddress.postalCode=10003&order.billingAddress.country=US&order.amount=100.00&capture=True&callbackUrl=https%3a%2f%2fapi.merchant.com%2forder%2fcomplete&redirectCancelUrl=https%3a%2f%2fapi.merchant.com%2forder%2fcancel&redirectConfirmUrl=https%3a%2f%2fapi-ci.quadpay.com%2forder%2fcomplete&X-QP-Signature=3f3oc6tACuGIs%2fX7bhWP3PkstZ8UmI6fSZ%2buPQE8Wfo%3d
Warning
You should not reuse checkout URL when starting orders. A new URL should be created each time.
Use case #1 — If a customer at checkout decides to go back and edit their cart, a new session should be created. They should not reuse the same checkout link.
Use case #2 — If a customer has modified their checkout between navigating away (canceling) and coming back (starting same checkout), that difference won't be shown in our system if the checkout link is reused.
Will request a merchant refund to the supplied order and apply it to the customer's payment plan. If you are a pay-on-ship/DFC merchant, you can only apply up to your captured amount.
Required fields are:
- orderId - Zip order ID to refund
- merchantId - Your identifier provided by Zip
- currency - The currency to issue the refund in
- amount - The amount of the refund (not the amount of the order!)
- merchantReference - An identifier in your system to reference this refund transaction
- callbackUrl - A URL that will receive the webhook request about the result of this operation
Sync vs Async Response
By default, requests to the /refund
endpoint will receive an asynchronous response. This means the initial HTTP request will receive a 204 response with the request body being sent to the callbackUrl specified in the initial POST request.
If you require a synchronous response, you can pass ?sync=true
as a query parameter. This will result in a success response code of 200 with a request body.
Example
Request URL: https://gateway.us.zip.co/orders/11111111-1111-1111-1111-111111111111/refund
POST Body:
{
"orderId": "11111111-1111-1111-1111-111111111111",
"currency": "USD",
"amount": 123.45,
"merchantReference": "ref",
"callbackUrl": "https://api.merchant.com"
}
Only for pay-on-ship/defer funds capture (DFC) merchants. This captures funds to reflect a fulfilled item in an order.
Required fields are:
- orderId - Zip order ID to capture
- merchantId - Your identifier provided by Zip
- currency - The currency to issue the capture in
- amount - The amount of the capture (not the amount of the order!)
- merchantReference - An identifier in your system to reference this capture transaction
- callbackUrl - A URL that will receive the webhook request about the result of this operation
- singleCapture - True if this will be the only capture for the order and you want the rest to automatically be voided.
Example
Request URL: https://gateway.us.zip.co/orders/11111111-1111-1111-1111-111111111111/capture
POST Body:
{
"orderId": "11111111-1111-1111-1111-111111111111",
"currency": "USD",
"amount": 123.45,
"merchantReference": "ref",
"callbackUrl": "https://api.merchant.com",
"singleCapture": false
}
Only for pay-on-ship/DFC merchants. This voids funds to reflect a cancelled item in an order.
Required fields are:
- orderId - Zip order ID to void
- merchantId - Your identifier provided by Zip
- currency - The currency to issue the void in
- amount - The amount of the void (not the amount of the order!)
- merchantReference - An identifier in your system to reference this void transaction
- callbackUrl - A URL that will receive the webhook request about the result of this operation
Example
Request URL: https://gateway.us.zip.co/orders/11111111-1111-1111-1111-111111111111/void
POST Body:
{
"orderId": "11111111-1111-1111-1111-111111111111",
"currency": "USD",
"amount": 123.45,
"merchantReference": "ref",
"callbackUrl": "https://api.merchant.com"
}
Merchants using the Standard Checkout with the mobile SDK will allow customers to use the Zip checkout without starting an order until this API call is made by the merchant. This same feature can be leveraged for standard online checkouts as well. The payment plan for the customer will not begin until a successful callback is received by the merchant for this operation. This operation does allow merchants to add shipping/taxes after the Zip checkout for the order. This will be processed with the currency of the original order. This functionality is enabled upon request. Please work with the integration team during onboarding if this is needed in your implementation.
Required fields are:
- orderId - Zip order ID to confirm
- merchantReference - An identifier in your system to reference this confirm transaction
- callbackUrl - A URL that will receive the webhook request about the result of this operation
Optionally, you can provide the following:
- amount - The new total order amount, including all shipping/tax amounts added in
- shippingAmount - The final calculated shipping amount
- taxAmount - The final calculated tax amount
Example
Request URL: https://gateway.us.zip.co/orders/11111111-1111-1111-1111-111111111111/confirm
POST Body:
{
"orderId": "11111111-1111-1111-1111-111111111111",
"merchantReference": "ref",
"callbackUrl": "https://api.merchant.com"
}
Merchants with a installment fee payment model (formerly MFPP) agreement can use this endpoint to determine the fee amount for a given order. The state/country for the customer should come from the customer's shipping address. When $0 is returned, then there is no fee and does not need to display within the merchant's UX. Required fields are:
- amount - The total order amount including shipping/tax amounts added in.
- currency - Defaults to USD if not provided.
- customerState- The state of the customer's shipping address.
- customerCountry - The country of the customer's shipping address. Defaults to "US".
- merchantId - The Merchant ID that fees need to be calculated for.
Example
Request URL: https://gateway.us.zip.co/orders/calculate-merchant-fees
POST Body:
{
"customerState": "NY",
"customerCountry": "US",
"currency": "USD",
"amount": 123.45,
"merchantId": "[Insert merchant Id]"
}
Sample response:
{
"merchantFeeForPaymentPlan": 1.00,
"currency": "USD"
}
Formatting Data for Signature Generation in POST and GET Requests
Processing Rules
Before sending the request, apply the following processing steps to the query parameter or form data.
Exclude X-QP-SIgnature Param
Exclude the X-QP-Signature parameter from any cryptographic hash or concatenation process. This key is used exclusively for validation and should not be included in the final message string,
Sort Data Alphabetically
Sort all remaining parameter names (keys) in alphabetical order.
Concatenate Keys and Values
Combine the sorted parameter names and their cooresponding values into a single string, without any separators.
Signature Validation
After the string is constructed, use it to generate or validate the signature as per the authentication method.
Examples
const crypto = require('crypto');
const url = '/orders/{orderId}/status?b=beta&c=charlie&X-QP-Signature=abc&a=alpha';
const secret = '<merchant-api-secret>';
let data = '';
// Extract query parameters and parse them
const queryParams = new URLSearchParams(url.split('?')[1]);
const keys = Array.from(queryParams.keys());
// Sort keys alphabetically
keys.sort();
// Build the data string, excluding "X-QP-Signature"
keys.forEach((key) => {
if (key === 'X-QP-Signature') return;
data += key;
data += queryParams.get(key);
});
// data should now = 'aalphabbetaccharlie' and can be used to generate signature
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
class Program
{
static void Main()
{
string url = "/orders/{orderId}/status?b=beta&c=charlie&X-QP-Signature=abc&a=alpha";
string secret = "<merchant-api-secret>";
string data = "";
// Parse query parameters
var query = url.Split('?')[1];
var queryParams = query.Split('&')
.Select(param => param.Split('='))
.ToDictionary(parts => parts[0], parts => parts[1]);
// Sort keys alphabetically
var keys = queryParams.Keys.OrderBy(k => k).ToList();
// Build the data string, excluding "X-QP-Signature"
foreach (var key in keys)
{
if (key == "X-QP-Signature") continue;
data += key + queryParams[key];
}
// data should now = 'aalphabbetaccharlie' and can be used to generate signature
}
}
$url = '/orders/{orderId}/status?b=beta&c=charlie&X-QP-Signature=abc&a=alpha';
$data = '';
$secret = <merchant-api-secret>;
$keys = array_keys($url);
sort($keys);
foreach ($keys as $key) {
if ($key === 'X-QP-Signature') {
continue;
}
$data .= $key;
$data .= $url[$key];
}
// data should now = 'aalphabbetaccharlie' and can be used to generate signature
Signing Requests
All operations are secured with an HMAC-SHA256 one-way hash that is performed using a shared secret key between Zip and the merchant. This secret key will be provided to you.
Signatures are generated based on the request type.
- POST JSON Requests sign the entire request body contents.
- POST Form Requests sign all the keys + values contained in the form request in alphabetical order except for the signature.
- GET Requests sign all the keys + values contained in the query string in alphabetical order except for the signature.
The header or query parameter name is always X-QP-Signature
.
Implementing this is language specific. Here are examples using C#, PHP, and Node.js that can generate these hashes for you:
public class HmacSha256Signature
{
private static Encoding encoding = Encoding.UTF8;
/// If your entire request body is passed in as bytes (e.g. POST JSON request), this will give you the correct hash
public string Compute(string secretKey, byte[] bytes)
{
using (var hmacsha256 = new HMACSHA256(encoding.GetBytes(secretKey)))
{
var hash = hmacsha256.ComputeHash(bytes);
return Convert.ToBase64String(hash);
}
}
/// Given a dictionary that contains all the values from a GET or form POST request, this will return your correct hash
public string Compute(string secretKey, IDictionary<string, string> values)
{
var builder = new StringBuilder();
// Form Keys Sorted Alphabetically
foreach (var item in values.OrderBy(i => i.Key))
{
if (!item.Key.Equals(Constants.SignatureKey, StringComparison.OrdinalIgnoreCase))
{
builder.Append(item.Key);
builder.Append(item.Value);
}
}
var message = builder.ToString();
return this.Compute(secretKey, encoding.GetBytes(message));
}
public string Compute(string secretKey, string json)
{
var bytes = encoding.GetBytes(json);
return this.Compute(secretKey, bytes);
}
/// This can take any object and turn it into a dictionary to be used for hash creation
public IDictionary<string, string> GenerateKeyValues(object model)
{
var jsonObject = JObject.FromObject(model, JsonSerializer.Create(Constants.KeyGenSerializerSettings));
var jTokens = jsonObject.Descendants().Where(p => p.Count() == 0);
var keyValues = jTokens.Aggregate(
new Dictionary<string, string>(),
(
properties,
jToken) =>
{
properties.Add(jToken.Path, jToken.ToString());
return properties;
});
return keyValues;
}
}
$signature = base64_encode(hash_hmac('sha256', $data, $secret, true));
// Calculate signature
const data = JSON.stringify(requestBody);
const key = 'your_api_key_here';
const signature = crypto
.createHmac('sha256', key)
.update(data)
.digest('base64');
Callbacks
All of the endpoints listed above include a callbackUrl
parameter. This URL will be used by Zip to update you on the result of each of these operations. The content of this webhook is as follows:
{
"timestamp": "2020-04-27T12:34:56.000000Z",
"merchantId": "[Insert merchant Id]",
"orderId": "11111111-1111-1111-1111-111111111111",
"currency": "USD",
"amount": 123.45,
"merchantReference": "1234-abc",
"success": true,
"metadata": {
"property1": "value1"
}
The merchantReference
value will match the value you provided for the original operation. You also may pass metadata
properties to add other important attributes you may need to use to identify your order.
Each of these callbacks will have a X-QP-Signature
header that contains the signature that you can use to verify and trust the HTTP request.
These callbacks are issued for every operation (order authorization, refund, void, and capture transactions).
Important! This data will also be provided to you as query string parameters on your supplied confirm URL when creating an order. You can use this to verify order completion within your system by verifying the signature and utilizing the other parameters to store the Zip order ID. Here is a sample URL showing how this information is added. Also note that we pass you the Zip customer information as part authorize order callbacks:
https://yoursite.com/order/complete?timestamp=04%2F27%2F2020%2023%3A27%3A22&merchantId=[Insert merchant Id]&orderId=11111111-1111-1111-1111-111111111111¤cy=USD&amount=123.45&merchantReference=1234-abc&test=False&success=True&customer.firstName=Test&customer.lastName=Test&[email protected]&customer.phone=%2B15555555555&customer.address.line1=123%20Main%20St&customer.address.city=New%20York&customer.address.state=NY&customer.address.postalCode=10000&customer.address.country=US&X-QP-Signature=PdkC29bSMbRG5DR6E0xQt781AgvaZa6Ov9V26Ez2OHU%3D
Test Data
You may use the below testing data while working with the Zip checkout in your development environment.
Field | Test Data |
---|---|
Phone | A US-based mobile phone number. You may use VOIP services for development if needed. |
Can be real or fake valid email address (‘@example.com’ for fake) | |
Verification Code | Code received via SMS |
Name | Anything |
Address | Anything valid |
Birthdate | Anything 22+ years old |
Social Security Number | 123-45-6789 |
Billing Address | Anything valid |
Card Holder Name | Anything |
Card Number | 4242 4242 4242 4242 |
Expiration date | 02 / 25 |
CVC | 222 |
Updated 15 days ago