> ## Documentation Index
> Fetch the complete documentation index at: https://waffo.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication & Signature FAQ

> Answers to the most common questions about Waffo API authentication: where to get your API key, how request and response signing works, webhook verification, A0001/A0002 troubleshooting, and RSA key generation.

The authentication and signing questions merchants ask most often. Full contracts:
[Acquiring API Specification](/docs/api-reference/introduction).

## Where do I get my API key?

Your API key is provided by Waffo during merchant onboarding. You can view it
any time on **Merchant Portal → Integration** (**Dev** or **Admin** role
required). Send it on every API request in the `X-API-KEY` header. Sandbox and
production use different API keys — switch both the API domain and the key
when you go live (see the [go-live checklist](/docs/en/developer-docs/getting-started/go-live)).

## What keys are exchanged between my system and Waffo?

At onboarding, you and Waffo exchange **RSA public keys** — each party keeps
its own private key:

| Key | Who holds it | Used for |
| - | - | - |
| Merchant private key | You (server-side only) | Signing your requests and your webhook responses |
| Merchant public key | Configured by you on **Merchant Portal → Integration** | Waffo verifies your signatures |
| Waffo private key | Waffo | Signing Waffo's responses and webhook notifications |
| Waffo public key | Copied by you from **Merchant Portal → Integration** | You verify Waffo's signatures |

Never send your private key to Waffo or commit it to a repository.

## How do I sign an API request?

The signature algorithm is **SHA256WithRSA**. Use your merchant RSA private
key to sign the **whole body message** of the request, and send the result in
the `X-SIGNATURE` header alongside `X-API-KEY`. Waffo verifies it with the
merchant public key you configured in the Portal and rejects the request with
an invalid-signature error if verification fails. All requests are HTTP POST
with `Content-Type: application/json` (UTF-8) over TLS 1.2 or above.

## How do I verify Waffo's API responses?

Waffo signs every response body with its own private key and returns the
signature in the response's `X-SIGNATURE`. Verify it with the **Waffo public
key** (from Merchant Portal → Integration) using SHA256WithRSA before
processing the response. If verification fails: investigate and contact
Waffo, and **stop sending new transactions until the issue is resolved** —
Waffo may already have processed your request even though you could not
accept the response.

## How do I verify webhook notifications?

Same mechanism as responses: each webhook request carries `X-SIGNATURE`,
signed with the Waffo private key. Verify with the Waffo public key —
SHA256WithRSA over the **raw request body** (do not JSON-parse and
re-stringify), signature decoded from Base64 — and always verify before
processing the event. The SDK's `handleWebhook()` does this for you; full
steps and Node.js/Go examples are in
[Webhook signature verification](/docs/en/developer-docs/webhook/signature-verification).

## Do I sign my webhook response back to Waffo?

Yes. Your response to a Waffo callback also carries an `X-SIGNATURE` header,
signed over the whole body message with your merchant private key. The body
is `{"message": "success / failed / unknown"}` per your processing result.

## What is the difference between A0001 and A0002?

| Code | Meaning | HTTP status | Points to |
| - | - | - | - |
| `A0001` | Invalid Api secret key | 401 | The `X-API-KEY` you sent is not valid for this environment |
| `A0002` | Invalid signature | 401 | The `X-SIGNATURE` failed verification against your configured public key |

## I get A0001. What should I check?

1. The `X-API-KEY` header is present and matches the key shown on **Merchant Portal → Integration**.
2. You are not sending a sandbox key to the production domain (or vice versa) — each environment has its own key.

## I get A0002. What should I check?

1. You signed the **whole body message** — exactly the bytes you send, not a re-serialized copy.
2. You signed with your merchant **private** key using **SHA256WithRSA**.
3. The merchant **public** key configured on **Merchant Portal → Integration** matches that private key.
4. After going live, you are using your production key pair — do not reuse the sandbox pre-generated keys.

## How do I generate an RSA key pair?

Use openssl ([https://www.openssl.org/source](https://www.openssl.org/source)), as given in the specification appendix:

```bash theme={null}
# generate private key
openssl genrsa 2048 | openssl pkcs8 -topk8 -nocrypt -out merchant_private_key.pem

# generate public key
openssl rsa -in merchant_private_key.pem -pubout > merchant_public_key.pem
```

The [go-live checklist](/docs/en/developer-docs/getting-started/go-live) also shows how
to strip the PEM wrappers into the one-line Base64 values the Portal public-key field expects.

## Who on my team can access the Integration page?

Viewing the API key and Waffo public key, and configuring your merchant
public key, all require the **Dev** or **Admin** role in the Merchant Portal.

## I have several merchant accounts. Can I use one API key across all of them?

No. Each API key is bound to a single merchant account (MID). A request
authenticated with account A's key can only operate on account A's data —
using it to query an order that belongs to account B fails, even though the
key itself is valid. When you operate multiple MIDs, keep one key pair per
account and always call with the key of the MID that created the order.

A related lookup pitfall: **subscription orders are not returned by the
one-time payment Order Inquiry**. Query subscriptions and their payment
orders through the subscription-specific inquiry APIs instead.

## Why does the SDK's pay-method query return an empty `payMethods` array?

When you query your pay-method configuration through the SDK, the
`payMethods` field in the result is always an empty array — this is expected.
The real data is in **`payMethodDetails`**: one entry per contracted pay
method, carrying `productName`, `payMethodName`, `country`, and
`currentStatus` (whether the method is currently available). Iterate
`payMethodDetails` to build the list of pay methods actually enabled under
your contract for each country.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.