ACH Test Accounts, Error Responses, and Status Event Postbacks
Be Advised
The use of live account info in a test environment is strongly discouraged.
Sandbox Testing
The following list of test account numbers are examples of values that can be used during testing.
Test Account Data - Declines for AchSale and AchReturn
Any 9 digit number can be used as the routing number to trigger the decline messages below. Any validly formatted account number that is not listed in the table will trigger an approval.
Account Number | Routing Number | Result Message |
---|---|---|
0000000000 | Any 9 digit number | Declined |
1111111111 | Any 9 digit number | The transaction contains invalid data or exceeds a limit |
2222222222 | Any 9 digit number | An upstream error occurred |
3333333333 | Any 9 digit number | The account number is invalid or failed validation |
Test Account Data - Invalid Routing Number for AchSale and AchReturn
Any validly formatted account number, other than those listed in the Declines table above, can be used to trigger the invalid routing number response. Any other 9 digit number used as the routing number will trigger an approval.
Account Number | Routing Number | Result Message |
---|---|---|
Any validly formatted account number not in the Declines table above | 000000000 | The routing number is invalid |
Postback ACH Status Events
ACH transaction status events are sent to the postback url configured on your account for receiving transaction responses. However we add the query string messageType=achEvent
to the end of your postback url, so you can distinguish the receipt of transaction responses from the receipt of ACH status events.
HMAC Signature Validation
Post-back results should be verified using the HMAC signature found in the request header.
- one-way SHA-512 message digest
- encrypted using a secret passphrase you setup with your integration specialist and is then base64 encoded
- digest is included in the request header as
hmac-signature
Verify HMAC signature
- Encrypt the request body using the SHA512 hash algorithm and your secret key
- Base64 encode the result
- Compare the resulting value with the
hmac-signature
header value
What's a 'secret passphrase'?
Similar to the OID and authToken, the secret passphrase is a unique variable that is tied to each individual merchant account.
You can use these samples to verify the HMAC signature
on your postback messages.
var express = require("express");
var cors = require("cors");
var crypto = require("crypto");
var app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors());
app.post("/PostBackListener?messageType=achEvent", (req, res) => {
var hmacSignature = req.header("hmac-signature");
var rawData = req.body;
var jsonData = JSON.stringify(rawData);
var signatureMatched = false;
if (hmacSignature) {
signatureMatched = verifyHmacSignature(hmacSignature, jsonData);
}
//if the hmac signature matched, the response body data is valid
if (signatureMatched) {
//do something with the transaction result
}
res.sendStatus(200);
});
function verifyHmacSignature(hmacSignature, data) {
//this is the secret pass phrase you supplied to ChargeItPro
var secretKey = "cipDemoListenerKey";
var hmac = crypto.createHmac("sha512", secretKey);
hmac.update(data);
return hmac.digest("base64") === hmacSignature;
}
console.log("listening on port 5555");
app.listen(5555);
import * as express from 'express';
import * as cors from 'cors';
import * as crypto from 'crypto';
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors());
app.post("/PostBackListener?messageType=achEvent", (req, res) => {
const hmacSignature = req.header("hmac-signature");
const rawData = req.body;
const jsonData = JSON.stringify(rawData);
let signatureMatched = false;
if (hmacSignature) {
signatureMatched = verifyHmacSignature(hmacSignature, jsonData);
}
//if the hmac signature matched, the response body data is valid
if (signatureMatched) {
//do something with the transaction result
}
res.sendStatus(200);
});
function verifyHmacSignature(hmacSignature: string, data: string): boolean {
//this is the secret pass phrase you supplied to ChargeItPro
const secretKey = "cipDemoListenerKey";
const hmac = crypto.createHmac("sha512", secretKey);
hmac.update(data);
return hmac.digest("base64") === hmacSignature;
}
console.log("listening on port 5555");
app.listen(5555);
<?php
$headers = getallheaders();
$body = file_get_contents("php://input");
$jsonData = json_encode(json_decode($body));
$signatureMatched = false;
if (array_key_exists("hmac-signature", $headers)) {
$hmacSignature = $headers["hmac-signature"];
$signatureMatched = verifyHmacSignature($hmacSignature, $jsonData);
}
// if the hmac signature matched, the response body data is valid
if ($signatureMatched) {
// do something with the transaction result
}
function verifyHmacSignature(string $hmacSignature, string $data) {
// this is the secret pass phrase you supplied to ChargeItPro
$secretKey = "yourSecretPassPhrase";
$hmac = hash_hmac("sha512", $data, $secretKey, true);
return base64_encode($hmac) == $hmacSignature;
}
[HttpPost]
public void PostBackListener()
{
var reader = new StreamReader(Request.Body);
var bodyContents = reader.ReadToEnd();
var transactionResult = JsonConvert.DeserializeObject(bodyContents);
var rawData = JsonConvert.DeserializeObject(bodyContents);
var jsonData = JsonConvert.SerializeObject(rawData);
//verify the hmac signature with our secret pass phrase
bool hmacSignatureExists = Request.Headers.TryGetValue("hmac-signature", out var hmacSignature);
bool signatureMatched = false;
if (hmacSignatureExists)
{
string signature = hmacSignature.ToString();
byte[] data = Encoding.UTF8.GetBytes(jsonData);
signatureMatched = VerifyHmacSignature(signature, data);
}
//if the hmac signature matched, the response body data is valid
if (signatureMatched)
{
//do something with the transaction result
}
}
private bool VerifyHmacSignature(string hmacSignature, byte[] data)
{
//this is the secret pass phrase you supplied to ChargeItPro
const string secretKey = "yourSecretPassPhrase";
using (HMACSHA512 hmac = new HMACSHA512(Encoding.UTF8.GetBytes(secretKey)))
{
byte[] computedHash = hmac.ComputeHash(data);
string computedSignature = Convert.ToBase64String(computedHash);
return hmacSignature == computedSignature;
}
}
Status Event Body
The message body of the postback request will contain a JSON object with a top level property called eventData
that contains an object with the following properties.
Name | Details | Description |
---|---|---|
eventId | Type: number | Unique id for the event |
eventDateTime | Type: number | Epoch timestamp for the event |
eventTypeDescription | Type: string | Description of the event |
transactionStatus | Type: string | Current status of the transaction |
settlementStatus | Type: string | Current settlement status of the transaction |
returnCode | Type: string | A descripion of the returned status of the transaction. NOTE: If this value is not null, the ACH transaction has been returned, and this event is actionable. |
externalTransactionId | Type: string | External transaction id submitted with original transaction, included for identification purposes |
uniqueTransId | Type: string | Unique transaction id returned in original transaction response, included for identification purposes |