Common pitfalls
Assumptions that look safe on the v1 contracts and are not.
Every v1 contract
| Assumption | Actual behaviour |
|---|---|
| HTTP 200 means it worked | On payouts, validation failures are returned as HTTP 200 with the error in the XML body. |
| A timeout means it failed | It means you do not know. Retry the identical request. |
| An unknown status is a failure | Leave it unresolved and keep polling. |
| Your polling timeout resolves the transaction | It resolves nothing. The transaction is still live and can still complete. |
| Sorting JSON keys before signing is harmless | It changes the signature wherever a body is signed. |
| A callback is proof | It is a notification. Re-read the transaction. |
| A payer landing on your return URL means paid | It means the browser came back. Read the status. |
Pay-ins — Apple Pay and Google Pay
| Assumption | Actual behaviour |
|---|---|
| Signing a pretty-printed body | Rejected. Sign the compact JSON you actually transmit. |
Dropping null fields before signing |
Rejected. Everything you send is signed. |
| Uppercase hex digest | Rejected. Lowercase hex. |
Branching on order_scheduled or redirect_pending |
Both are deprecated and handled as pending. Act on redirect_data: send the buyer to redirect_data.url whenever it is present; some payments start only there and fail after 10 minutes. |
| Waiting for a webhook as the only signal | Poll GET /sdk-partner/alternative-mobile-pay/mobile-pay/{id}/status from 5 seconds after the create until the status is final. |
Treating refunded as a full refund |
It appears for partial refunds too. Read refunded_amount. |
Missing X-Signature on an error response |
Authentication failures are rejected before the signing key is resolved. Absence is expected there. |
Pay-ins — card payments
| Assumption | Actual behaviour |
|---|---|
status: 1 means paid |
It means authorization: funds are locked, not taken. The payment completes at 3. |
| Statuses are strings, as elsewhere in pay-ins | They are integers here. |
| Signing the JSON before base64 | sign is computed over the base64 string, not the JSON behind it. |
| Reusing the Apple Pay or Google Pay credentials | Card payments have their own api_key and secret_key; the others do not authenticate. |
api_key sent beside data |
It belongs inside the encoded data document. |
8 is a failure |
The provider had a temporary problem and returned a payment form link. Redirect the payer. |
| A refund that has not settled is lost | 15 means refund processing, which can take 1–7 days. |
Payouts
| Assumption | Actual behaviour |
|---|---|
Reading OK as success |
OK means accepted for processing. The result comes later, by callback or check. |
Reading PROCESSING as a problem |
It is also normal, and a core timeout is reported the same way. |
Retrying with a changed amount under the same id |
The original payout is returned silently. Your new amount is ignored and no error is raised. |
| Joining signature fields with a delimiter | The hash is MD5 of the fields concatenated with nothing between them, then secret_key. |
| Leaving an optional field empty and including it in the hash | Empty fields are skipped. Include only what the endpoint's formula lists. |
Parsing the <error> text |
It is free-form prose with no stable codes. Treat it as a log line, not a branch condition. |