Off-Site Gateway Checkout

Direct submit

This option is great for cases where there is only one item that needs to be purchased. Common use cases are: donation purposes, paying a one-time subscription fee, or buying a single product. In order to initiate direct submission, a form must be directly submitted to the Off-Site Gateway endpoint payment/pay with all required checkout information. The user will be redirected to Dwolla where the checkout process will be completed.

HTTP request


Request parameters
keyyesstringApplication key.
signatureyesstringHMAC-SHA1 hex hash of the app key, timestamp, and order ID.
timestampyesstringTimestamp of the generated purchase order. Represented as the UNIX epoch time.
destinationIdyesstringDwolla ID of the account receiving the funds. Format will always match 812-xxx-xxxx.
amountyesstringAmount of order. Must be at least $0.01.
nameyesstringName of the item (100 character limit)
descriptionyesstringDescription of the item (200 character limit)
callbacknostringURL to POST the transaction response to after the user authorizes the purchase. If not provided, will default to the Payment Callback URL set for the consumer application. If a URL could not be found in the application settings and this parameter is not set, a postback error will be induced. Only applies if checkoutWithApi is false (Pay Later flow).
redirectnostringURL to return the user to after they authorize or cancel a purchase. If not provided, the URL will default to the Payment Redirect URL set in the application’s settings. If no URL could be found, an error will be induced.
allowFundingSourcesnostringDefaults to false, set to true to enable guest checkout and bank-funded payments. This would allow users without a Dwolla account to pass through the checkout flow.
checkoutWithApinostringDefaults to false (and the Pay Now flow), set to true to use the Pay Later flow.
orderIdnostringOptional string to identify the transaction (255 character limit).
testnostringDefaults to false, set to true to flag the PO for testing. Does not affect account balances nor are any e-mails sent. The transaction ID returned from a PO with test flagged will always be 1.
taxnostringTax applied to the order. Must be $0 or greater.
shippingnostringShipping total for the order. Must be $0.00 or more.
facilitatorAmountnostringAmount of the facilitator fee (will override the amount in the application’s settings). If set to 0, the fee will be disabled. Must not exceed 25% of the transaction total.
notesnostringNote to attach to transaction (250 character limit).

HMAC-SHA1 Hashing - Required

The hexadecimal hash is generated from the ampersand delimited text of the consumer key, Unix timestamp, and orderId, even if an order ID is not provided. The text is then hashed using the consumer secret for the application as the hash key. The signature must be unique per request, so a new timestamp will need to be provided for each request. Below are examples on how to generate HMAC-SHA1 hashes in languages commonly used with web-stack development:

not applicable
def sign_request():
  from hashlib import sha1
  import hmac
  import binascii
  import time

  key       = 'YOUR_APPLICATION_KEY'
  timestamp = int(time.time())
  order_id  = 1
  text      = key + '&' + timestamp + '&' + order_id

  hash =, text, sha1)

  # We format the string to exclude the last character which is a newline delimiter.
  return binascii.b2a_base64(hashed.digest())[:-1]
var crypto    = require('crypto')
  , key       = 'abcdeg'
  , order_id  = 1
  , timestamp = (new Date).getTime()
  , text      = key + '&' + timestamp + '&' + order_id
  , hash;

hash = crypto.createHmac('sha1', secret).update(text).digest('hex');
require 'rubygems'
require 'base64'
require 'cgi'
require 'hmac-sha1'

timestamp =
order_id  = 1

signature = "#{key}&#{timestamp}&#{order_id}"

hmac =
hash = CGI.escape(Base64.encode64("#{hmac.digest}\n"))
$key        = "YOUR_APPLICATION_KEY";
$timestamp  = time(); // example: 1390408833
$order_id   = 1;

$signature  = hash_hmac('sha1', "{$key}&{$timestamp}&{$order_id}", $secret);

Example form

<form accept-charset="UTF-8" action="" method="post">

<input id="key" name="key" type="hidden" value="abcdefg" />
<input id="signature" name="signature" type="hidden" value="abcd" />
<input id="timestamp" name="timestamp" type="hidden" value="1323302400" />
<input id="callback" name="callback" type="hidden" value="" />
<input id="redirect" name="redirect" type="hidden" value="" />
<input id="test" name="test" type="hidden" value="false" />
<input id="name" name="name" type="hidden" value="Purchase" />
<input id="description" name="description" type="hidden" value="Description" />
<input id="destinationid" name="destinationid" type="hidden" value="812-713-9234" />
<input id="amount" name="amount" type="hidden" value="1.00" />
<input id="shipping" name="shipping" type="hidden" value="0.00" />
<input id="tax" name="tax" type="hidden" value="0.00" />
<input id="orderid" name="orderid" type="hidden" value="188375" />

<button type="submit">Submit Order</button>

Depending on the checkout flow utilized, the user will be redirected to the redirect URL with either completed transaction details or a confirmation page on the merchant’s webpage wherein they will complete their transaction.

If any errors incur, they will be passed as parameters to the redirect URL:

URL example - success and failure
// Success


// Failure


Possible error_descriptions:
"Payment has already been generated for application, timestamp, and order ID."
"Invalid callback URL"
"Invalid application signature."
"Invalid timestamp."
"Invalid application credentials."
"There are insufficient funds for this transaction."
"User Cancelled"
"Invalid destination user."

Example return

// First, check for any errors
if (array_key_exists("error", $_GET)) {
    // find out what happened:
    $error_description = $_GET['error_description']; // "User Cancelled"

// Create a function that verifies offsite-gateway signature hash against server-provided hash.
function verifyGatewaySignature($proposedSignature, $checkoutId, $amount) {
    global $secret;
    $key        = "Your key here";
    $secret     = "Your secret here";
    $amount = number_format($amount, 2);
    $signature = hash_hmac("sha1", "{$checkoutId}&{$amount}", $secret);
    return $signature == $proposedSignature;

// Second, extract the checkoutId, amount, and signature of the checkout:
$checkoutId = $_GET['checkoutId'];
$amount = $_GET['amount'];
$signature = $_GET['signature'];

// Third, validate the signature before you do anything with the data.
$signatureValid = verifyGatewaySignature($signature, $checkoutId, $amount);
if (!$signatureValid) {
    exit("Signature is not valid!");

$status = $_GET['status'];
if ($status == "Completed") {
    // do something useful with the checkout results:
    echo $status;


Financial institutions play an important role in the Dwolla network.

Dwolla, Inc. is an agent of Veridian Credit Union and Compass Bank and all funds associated with your account in the Dwolla network are held in pooled accounts at Veridian Credit Union and Compass Bank. These funds are not eligible for individual insurance, including FDIC insurance and may not be eligible for share insurance by the National Credit Union Share Insurance Fund. Dwolla, Inc. is the operator of a software platform that communicates user instructions for funds transfers to Veridian Credit Union and Compass Bank.