# Hash calculator

> Work out the hashCheck a request carries, and see the exact string it is computed from.

Source: https://hub.ozow.com/integration-methods/apis/deprecated-integrations/hash-calculator/

Every request that moves money carries a `hashCheck`, and a request whose hash does not match is
rejected. The rejection does not say which field was wrong, so this shows you the string the hash is
computed from, one field at a time.

It works in both directions. **Build it** fills in the fields you send and shows the string they
concatenate to. **Check mine** goes the other way: give it what your own code produced, and it says
where that leaves the documented string and names the mistake that would explain it.

> ⚠️ **Important**: Neither of those needs your private key, and the page asks for one only if you
> choose the last of three options. It builds the string with a placeholder in place of the key,
> which is all you need: a hash is determined entirely by the string it is computed from, so if your
> string matches this one, your hash matches too. Comparing strings finds every mistake except a
> wrong key, because every other mistake changes the string before the key is reached.

> ⚠️ **Before you paste a key anywhere**: check the address bar. This page is the only one on this
> site that will ever ask for one, it holds it in the tab and nowhere else, and it still asks you to
> try the string comparison first. A page imitating this one would ask sooner and explain less.

[Hash calculator](https://hub.ozow.com/integration-methods/apis/deprecated-integrations/hash-calculator/), a tool on this page.

## What usually goes wrong

The key is rarely the problem. In order of how often they occur:

| Cause | What it looks like |
|---|---|
| **Field order** | The fields are concatenated in a fixed order, not the order your object happens to serialise in. Reordering them changes the hash. |
| **Amount format** | A payin writes the amount with two decimals, `100.00`. A payout writes it in cents, `10000`. |
| **A blank optional field** | An empty field contributes nothing at all. It does not contribute a placeholder, a space or the word `null`. |
| **Lowercasing** | Payin, payout and verification hashes lowercase the whole concatenated string, including the key. Refunds do not: see below. |
| **The key itself** | Test and production keys differ. A hash built with the wrong one fails in exactly the same way as a hash built in the wrong order. |

## Three things the notification hashes do differently

- **The payout notification takes `customerMerchantReference`**, where the payout request takes
  `customerBankReference`. Check which one you are reading before you hash it.
- **The payout notification's two statuses go in as integers**, not as their names.
- **A voucher payout appends the voucher pin after the key**, so the key is not last for that one hash.

The refund notification also arrives with the account number already masked, so verify it with the
masked value you received rather than the number you sent.

## Refunds do not lowercase

Payin, payout and payout verification hashes lowercase the entire concatenated string before
hashing. **A refund hash does not.**

Apply the payin rule to a refund and your hash is rejected the moment your refund reason or notify
URL contains a capital letter. Build the refund string exactly as your values are, with no
lowercasing.

## Which direction the hash goes

A **request** hash is one you build and send. Ozow rejects the request if it does not match.

A **notification** hash is one you check on something Ozow sent you, and **its field order is not
the same as the request's**. Reusing a request's order to verify an incoming notification rejects
every notification you receive, which is the single most expensive way to get this wrong: your
integration looks fine until money starts moving.

## One API does not use a hash

One API authenticates every call with an OAuth 2.0 bearer token, and its requests carry no
`hashCheck` field.

Its webhooks are not verified with a hash either. One API delivers them through
[Svix](https://www.svix.com/), which signs each one with an HMAC over the `svix-id`,
`svix-timestamp` and body. Verify it with the secret from the Get Webhook Secret endpoint, using the
Svix libraries. [Redirect to Ozow](https://hub.ozow.com/integration-methods/apis/payin/redirect-to-ozow.md) covers the
headers and how to check them.

Use this page for the Payments API and the Payouts API.

## Where each hash is used

| Request | Concatenates | Key |
|---|---|---|
| Payments API payin request | 32 fields, from `siteCode` to `tokenProfileId` | Your private key |
| Payments API payin notification | 13 fields, from `siteCode` to `statusMessage` | Your private key |
| Payments API refund request | `transactionId`, `amount`, `refundReason`, `notifyUrl` | Your private key |
| Payments API refund notification | 8 fields, from `refundId` to `statusMessage` | Your private key |
| Payouts API payout request | 11 fields, from `siteCode` to `identityType` | Your API key |
| Payouts API payout verification | The payout fields, with `payoutId` in front | Your API key |
| Payouts API payout notification | 6 fields, from `payoutId` to `payoutStatus.subStatus` | Your API key |

The payin hash is described in full in [Redirect to
Ozow](https://hub.ozow.com/integration-methods/apis/payin/redirect-to-ozow.md), and the payout hash in [Send a
payout](https://hub.ozow.com/integration-methods/apis/payout/send-a-payout.md).

> ℹ️ **Note**: Compute the hash on your server, never in browser JavaScript. A hash built in the
> browser needs the private key in the browser, and anything that reaches the client can be read by
> anyone holding the client.