Top 50 Integration Mistakes
Each one lists the problem, the root cause, and the fix. Skim these before you go live.
Signing the wrong amount string
Requests fail with 401 Invalid authentication hash even though the amount looks correct.
The hash is over the raw string; "1499.0" and "1499.00" differ.
Hash and send the identical two-decimal string, e.g. "1499.00".
Wrong field order in the hash
Signature never validates.
Fields were concatenated in the wrong order or a field was omitted.
Follow the exact per-endpoint formula, e.g. key|merchant_transaction_id|transaction_amount|salt.
Using the wrong salt for the environment
Auth works in sandbox but fails in production (or vice versa).
Sandbox and production have different salts.
Load the salt from environment config per environment; never share credentials across environments.
Exposing the secret salt to the client
Anyone can forge valid requests.
The salt was embedded in browser or mobile code.
Sign only on the server; never ship the salt to a client.
Trusting the browser redirect as proof of payment
Orders marked paid that were never actually paid.
The redirect can be spoofed or interrupted.
Confirm server-side via verified webhook or Transaction Status before fulfilling.
Not verifying the webhook signature
Fraudulent or malformed payloads are processed.
The handler acted on the payload without checking the hash header.
Recompute the hash and constant-time compare before acting.
Non-idempotent webhook handling
Duplicate fulfilment or double emails.
The same event is delivered more than once and processed each time.
Deduplicate by transaction_id + event type; make processing idempotent.
Slow webhook responses
Webhooks get retried repeatedly.
The handler does heavy work before returning 200 and times out.
Verify, enqueue, return 200 immediately; process asynchronously.
Reusing order IDs
Initiate Payment returns Already exists.
merchant_transaction_id must be unique per merchant.
Generate a unique order id per attempt; reconcile retries on your side.
Refunding by merchant_transaction_id
Refund returns 404 Transaction not found.
Refunds reference the GrowthBnk transaction_id, not your order id.
Store the returned transaction_id and use it for refunds.
Refunding a non-successful transaction
400 Refund is allowed only for successful transactions.
Only SUCCESS payments are refundable.
Check status is SUCCESS before refunding.
Over-refunding
400 exceeds refundable balance.
Sum of refunds exceeded the original amount.
Track the remaining refundable balance; cap refunds at it.
Assuming a refund webhook exists
Refund completion never detected.
No dedicated refund webhook is emitted.
Poll Refund Status until refund_completed_at is set.
Treating INITIATED as paid
Orders fulfilled that were never completed.
INITIATED is not terminal.
Only fulfil on SUCCESS.
Expecting PENDING/FAILED status strings
Status comparisons never match.
GrowthBnk normalises to INITIATED/SUCCESS/FAILURE/DROP/CANCELLED.
Compare against the five documented statuses.
Tight polling loops
Rate limited (429) and wasted resources.
Fixed, rapid polling without backoff.
Use webhooks primarily; poll with exponential backoff and stop at terminal states.
Never stopping polling
Endless status calls.
No terminal-state check.
Stop once status is SUCCESS/FAILURE/DROP/CANCELLED.
Ignoring the success flag
Missing gateway-init failures.
Only the HTTP status was checked; body had success:false.
Branch on the success flag as well as the HTTP status.
Not handling the status array
Wrong status read for retried orders.
Transaction Status returns an array (newest first).
Use the most recent element for the current outcome.
Retrying on timeout without reconciling
Double charges.
A timed-out payment may have succeeded.
Check Transaction Status before retrying a timed-out request.
Blindly retrying non-idempotent calls
Duplicate side effects.
Retries without a stable order id.
Keep a unique order id so retries are safe/idempotent.
Amount below the minimum
400 Invalid amount or less than 1 rupee.
Amount was under ₹1.00.
Send at least 1.00 with two decimals.
Missing customer contact
Validation error on Initiate Payment.
Neither email nor phone was supplied.
Provide at least one of customer_email or customer_phone.
Invalid phone format
Validation rejects the customer phone.
Phone contained non-digits or wrong length.
Send digits only, 7–15 characters.
Sending amount as a client-supplied value
Price tampering.
The amount came from the browser.
Compute the amount server-side from your order and sign that.
No routing rule for the amount
400 No Routing Rule found for this amount.
No active rule covers the request.
Configure a routing rule that covers the amount band.
Hardcoding a gateway
Brittle integration.
Assuming a specific gateway is selected.
Let routing choose; never depend on a specific gateway in code.
Cancelling a processed transaction
400 cannot be cancelled.
Only INITIATED transactions can be cancelled.
Check status before cancelling.
Not whitelisting server IPs
403 IP is not whitelisted in production.
The calling IP is not on the allow-list.
Add your server egress IPs to the whitelist and keep it updated.
Calling a disabled module
403 module access denied.
The module is not enabled on the account.
Ask support to enable the module, then retry.
Using HTTP instead of HTTPS
Credentials/requests exposed.
Plaintext transport.
Always use HTTPS/TLS for API calls and webhooks.
Logging secrets or card data
Compliance/security exposure.
Salt, full PAN or raw signed requests were logged.
Log only IDs and status; redact secrets.
Non-constant-time hash comparison
Timing side-channel on verification.
Used == to compare hashes.
Use hash_equals / compare_digest / timingSafeEqual.
Localhost webhook URL
Webhooks never delivered.
The URL is not publicly reachable.
Use a public HTTPS URL; use a tunnel for local testing.
Assuming webhook ordering
State machine corrupted by out-of-order events.
Events can arrive out of order.
Use timestamps and treat the latest verified terminal state as authoritative.
Creating a new HTTP client per request
High latency and connection churn.
No connection reuse.
Reuse one client/SDK instance for the process lifetime.
No timeouts on requests
Threads hang on slow gateways.
Default/no timeout.
Set explicit connect/read timeouts and pair with retries.
Hardcoding base URLs
Painful environment switches.
URLs embedded in code.
Read the base URL from environment configuration.
Committing credentials to git
Secret leakage.
Keys/salt in source control.
Use environment variables / secret managers and gitignore config.
Not storing the transaction_id
Cannot refund or reconcile later.
Only the order id was saved.
Persist the GrowthBnk transaction_id with every order.
Ignoring request/response IDs
Slow support resolution.
No traceable identifiers logged.
Log request and transaction IDs for every call.
No reconciliation job
Silent mismatches accumulate.
Relying solely on real-time signals.
Run a periodic reconciliation against status/settlement data.
Swallowing errors silently
Failures go unnoticed.
Exceptions caught and ignored.
Log, alert and surface actionable errors; branch on status/message.
Not validating inputs before signing
Wasted calls and confusing 400s.
Bad data sent to the API.
Validate amount, customer and IDs before building the request.
Assuming sandbox IDs exist in production
Lookups fail after go-live.
Environments are isolated.
Never hardcode IDs; use IDs from the current environment.
Skipping the go-live checklist
Preventable production incidents.
Rushed launch.
Complete the Go Live Checklist before ramping traffic.
No monitoring or alerting
Outages detected late.
No dashboards/alerts on failures.
Monitor success rate, webhook delivery and error rates; alert on spikes.
Not handling duplicate callbacks
Double processing.
Callbacks/webhooks can repeat.
Deduplicate and make handlers idempotent.
Confusing customer_transaction_id with transaction_id in refunds
Refund fails or targets the wrong record.
The two IDs were mixed up.
Use merchant_transaction_id for status/cancel and GrowthBnk transaction_id for refunds.
Not testing failure and edge cases
Breaks in production on the first decline/timeout.
Only the happy path was tested.
Test failures, timeouts, pending, refunds and duplicate webhooks in sandbox before go-live.
See also Troubleshooting and the FAQ.