> ## 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.

# Production Public Keys & API Operation Types

> Submitting and rotating your production public key yourself: the exact format the field accepts, why the Portal asks you to sign a challenge string, what the three API operation types mean and why Withdraw must stand alone, and how old and new keys coexist during a rotation.

Three pages divide the integration-security surface:

| Question | Page |
| - | - |
| How do I sign a request, verify a response, and what do `A0001` / `A0002` mean? | [Authentication & Signature FAQ](/docs/en/support/auth-signature-faq) |
| How do I set notification URLs and restrict access by IP? | [Portal Self-Service Security Configuration](/docs/en/support/portal-security-config-faq) |
| How do I submit or rotate a **production** public key, and what is an API operation type? | This page |

## Can I add my production public key myself?

Yes. On **Settings → Integration** there is a **Configure** button above the *Merchant Sign
Configuration Details* table. You submit the key yourself, from the Portal, without emailing
anyone a file.

Self-service submission and Waffo review are not alternatives — you get both. When you confirm,
a dialog says so explicitly: *once submitted, it will enter the review process*. The key is
registered immediately as a new version at `Pending`, and becomes usable when it reaches
`Active`. Your existing key keeps verifying your traffic throughout, so a submission never
interrupts live payments.

| Status | Meaning |
| - | - |
| `Pending` | Submitted, awaiting review |
| `Active` | Approved — verifying your request signatures |
| `Rejected` | Not approved; the reason is recorded against the version |

## What exactly does the public key field accept?

It is strict, and the dialog shows you a live character counter against the required length so
you can tell before submitting:

| Requirement | Value |
| - | - |
| Algorithm | SHA256withRSA |
| Key length | RSA 2048-bit |
| Encoding | Base64, X.509 |
| String length | Exactly **392 characters** |
| Wrappers | None — no `BEGIN PUBLIC KEY` / `END PUBLIC KEY` lines |
| Whitespace | None — no spaces, no line breaks |

The 392 is not advisory. The field validates only once your input is exactly that length, and
the counter turns red above it. What this means practically: **take the one-line Base64 body of
your PEM file and nothing else.** A pasted PEM file will be the wrong length because of its
header, footer and line breaks. The
[go-live checklist](/docs/en/developer-docs/getting-started/go-live) shows how to strip a PEM down
to the value this field wants, and the
[Authentication & Signature FAQ](/docs/en/support/auth-signature-faq) has the `openssl`
commands to generate the pair.

Three distinct rejections come out of validation, and they tell you which of the above failed:
a **length** mismatch, an **illegal character** (something that is not valid Base64 — usually a
stray space or a wrapper line), and an **algorithm** mismatch (a valid Base64 string that is
not a 2048-bit RSA public key at all).

## Why does the Portal make me sign a challenge string?

To prove you hold the private key that pairs with the public key you are uploading. Without it,
anyone able to reach the form could register a key they do not control.

The flow inside the dialog:

1. Paste your public key. It is format-checked first.
2. Once it passes, an **Ownership Verification** section appears with a challenge string of the
   form `WAFFO_VERIFY_{your merchant ID}`, with a copy button.
3. Sign that string locally with the **private key** paired to the public key you just pasted,
   using SHA256withRSA, and Base64-encode the result.
4. Paste the signature into **Signature Result**.

Confirm stays disabled until all three of an operation type, a valid public key and a signature
are present. The signature is then verified with the public key you submitted — a mismatched
pair is rejected at submission, so you find out immediately rather than after review.

Sign the challenge string **exactly as shown**, with no trailing newline. That is the single
most common cause of a mismatch on a key pair that is in fact correct.

## What are the three API operation types?

An operation type scopes what a key pair — and the API key issued alongside it — is allowed to
be used for. The dialog offers three buttons:

| Button | Scope |
| - | - |
| Payin | Payment, refund and the related query APIs |
| Payout | Payout and transfer APIs |
| Withdraw | Withdrawal APIs |

You can select **Payin and Payout together**, and the Portal registers that as a single
combined scope covering both. You will see it in the table afterwards as a Payin-and-Payout row
rather than as two rows.

## Why can't I combine Withdraw with Payin or Payout?

Because Withdraw is a different class of operation: it moves your own settled funds out, rather
than moving a customer's money in or paying a third party. Waffo keeps it on its own credential
so that a key used by your checkout or payout service can never be used to move your balance
out, and so that compromising one does not imply the other.

The dialog enforces this rather than warning about it: selecting **Withdraw** clears Payin and
Payout, and selecting either of those clears Withdraw. There is no combination that includes
Withdraw with anything else. So if you operate withdrawals through the API, plan on **at least
two credentials** — one for your payments traffic and a separate one for withdrawals — and keep
them in different places in your secret store.

## How does key rotation work without downtime?

The important mechanic: **a submission never edits a key in place.** Each accepted submission
creates a new version, and each version gets its **own API key**. Which public key verifies a
given request is decided by the `X-API-KEY` on that request — one API key, one public key.

That is what makes a clean rotation possible, because more than one version can be `Active` at
the same time under the same operation type:

1. Generate a new key pair. Keep the old one in place and in use.
2. Submit the new public key from the Portal. It appears as a new version at `Pending`. Your
   traffic continues to verify against the old key, unaffected.
3. When it reaches `Active`, read its **API key** from the *Merchant Sign Configuration Details*
   table.
4. In your integration, switch the `X-API-KEY` **and** the signing private key **together**, as
   one change. They are a set; swapping one without the other produces `A0002`.
5. Verify traffic on the new credential, then stop using the old one.

Because both versions are `Active` during step 4, you can cut over service by service, or run a
canary, without a window where neither credential works. And because the two are matched pairs,
a rollback is just switching the header and the private key back.

Do not skip step 3. Reusing your existing API key with a newly submitted public key is the most
common rotation failure — the old API key still points at the old public key, so your new
signatures fail verification while everything looks correctly configured in the Portal.

## Can I submit the same public key twice?

No. A public key that is already registered under the same operation type is rejected rather
than creating a duplicate version. This is a guard against accidentally "rotating" onto the key
you are already using, which would look like a successful rotation and change nothing.

The same key **may** be submitted under a *different* operation type, though a separate pair per
scope is the better practice — it is the point of scoping them.

## Is there a limit on how many keys I can hold?

Yes, there is a cap per operation type, and a submission that would exceed it is refused with a
dedicated error. The count includes versions that were rejected, so abandoned attempts consume
room. Two habits keep you well clear of it: rotate deliberately rather than exploratively, and
fix a rejected submission's cause before resubmitting.

## Where do I see my past submissions and why one was rejected?

**Historical Versions**, below the configuration table on the same page. Each row is one
submission:

| Column | Content |
| - | - |
| Version | `V1`, `V2`, … per operation type |
| API Operation Type | The scope it was submitted for |
| Merchant Public Key | The key, masked, with a copy action |
| Status | `Pending` / `Active` / `Rejected` |
| Submission Time | When you submitted it |
| Reject Reason | The reason, for rejected versions |
| Action | **View Reason** on a rejected version, opening the full text |

Rejection is not terminal and there is no cap on attempts: read the reason, fix it, and submit
again as a new version. The list is paginated, newest first, so a long rotation history stays
readable.

## Who is told when a key is activated or rejected?

Your account's **Super Admin** users receive an email either way. An activation email confirms
the operation type and the activated key in masked form; a rejection email carries the reason.

Plan for this if the person who submits keys is not the person who holds the Super Admin
mailbox — the submitter learns the outcome from the Portal, and the email lands elsewhere. Check
Historical Versions rather than waiting on a forwarded email.

## Do I need to configure a public key for sandbox?

No, and you cannot. The Configure button and Historical Versions appear only outside sandbox —
in sandbox, Waffo generates the pair for you when the sandbox is provisioned, and the sandbox
key material is shown to you directly on the Integration page so you can start signing
immediately.

Two consequences when you go live:

* **Do not reuse sandbox key material in production.** Generate a fresh pair; the production
  public key must be one you submitted and whose private key only you hold.
* **Switch the API domain and the credential together.** Each environment has its own API
  address and its own API key; the address is shown on the Integration page for the environment
  you are in.

## Why is my production public key masked, and my sandbox private key not?

Because they are not the same kind of secret. In production the page masks key material by
default with a show/hide control, so opening the Integration page in a shared screen or a
recorded call does not expose it. A copy action is always available without unmasking, so you
never need to reveal a value merely to move it into your secret store.

Your **production private key is never on this page** and never should be — Waffo does not hold
it. If you have lost it, no Portal action recovers it: generate a new pair and rotate as above.

## What is in the Merchant Sign Configuration Details table?

One row per operation type you hold, tying the credential to the URLs it uses:

| Column | What it is |
| - | - |
| API Operation Type | The scope of this row |
| Merchant Public Key | The public key currently verifying your requests for this scope, masked |
| API Key | The `X-API-KEY` to send with requests in this scope, masked |
| Refund Notify URL | With an **Edit** action |
| Chargeback Notify URL | With an **Edit** action |
| Tokenization Notify URL | With an **Edit** action |

The URL columns are configuration rather than key material, and they change with no review —
see [Portal Self-Service Security Configuration](/docs/en/support/portal-security-config-faq) for
their validation rules and scope. If you hold a client-side credential for card collection, it
appears in this table as its own row.

***

Signature still failing after a rotation? Work through the `A0002` checklist in the
[Authentication & Signature FAQ](/docs/en/support/auth-signature-faq) — in a
post-rotation failure the cause is almost always an API key and private key that no longer
belong to each other.


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