How to handle webhook errors

Auto-retry schedule, diagnostics when the webhook does not fire, and FAQs covering event ordering, IP allowlist, replay, and rate limits.

||

SePay auto-retries when your endpoint errors. This page covers the retry schedule, how to diagnose when the webhook doesn't fire, and frequent questions.

Retry schedule

When the webhook has "Auto retry on server error" enabled, SePay retries if:

  • Endpoint not reachable (DNS, connection refused, timeout)
  • Server returns an HTTP status outside 200-299

Spacing between attempts grows by Fibonacci:

AttemptWaitTotal
1 (initial)now0 min
21 min1 min
31 min2 min
42 min4 min
53 min7 min
65 min12 min
78 min20 min
8 (last)13 min33 min

SePay makes 8 total delivery attempts (the initial attempt plus 7 retries), wrapping up around 33 minutes if every attempt fails. The webhook is then marked Failed and SePay sends an alert (if you've configured one). Queue items older than 5 hours aren't picked up by the cron job; this is a safety window that normally isn't reached because the 33-minute cap kicks in first.

Fast endpoint tip

Return 200 immediately, then push the payload into a queue for later processing. Don't block the response on heavy work.

Error categories

Connection errors

ErrorWhat it isHow to fix
DNS ErrorDomain not foundCheck URL, DNS
Connection RefusedServer refusedCheck server is running, port, firewall
TimeoutMore than 30 seconds with no responseOptimize endpoint, return 200 first then process
SSL ErrorCertificate problemCheck cert, CA chain, no self-signed

HTTP errors

StatusWhat it isHow to fix
401Wrong authCheck API Key, Secret Key
403BlockedAdd SePay IPs to whitelist
404Wrong URLCheck the path (case-sensitive)
405Wrong methodEndpoint must accept POST
500Server errorCheck logs
502/503Server not readyMaintenance or overload

Response speed

TimeNote
< 1 secondGood
1-5 secondsShould improve
> 5 secondsLikely to timeout

cURL codes

The log records error_code (cURL code) + response_time_ms per delivery.

CodeErrorNotes
6DNSWrong domain or DNS not pointing
7Connection RefusedServer off, wrong port, firewall block
28TimeoutResponse took more than 30 seconds
35SSLTLS handshake error
51SSL CertInvalid certificate
55Send ErrorConnection lost while sending
56Receive ErrorConnection lost while receiving
60CA CertCannot verify CA

Diagnose when webhook doesn't fire

Webhook is enabled but your server isn't receiving requests? Go top to bottom. The issue is usually in the first few steps.

1. Webhook still enabled

Dashboard → Webhooks, the Status column must say On. Saving a webhook doesn't auto-enable if you turned it off; check the toggle after each edit.

2. Is the URL reachable

Click Test send. SePay sends a sample payload, results show immediately.

ResultCommon cause
SuccessURL is fine, go to step 3
DNS ErrorDomain not pointing or URL typo
Connection RefusedServer not listening on that port
TimeoutFirewall blocking or server too slow
SSL ErrorCertificate expired or invalid
HTTP 4xx / 5xxServer received but returned an error, open the log detail

3. Event type matches

Webhook has 3 types: All (in and out), Money in only, Money out only.

A Money in only webhook tested with a withdrawal won't fire.

Money out only has additional constraints: only Sacombank, TPBank, VietinBank are supported, and TKP (memo-based VA) must be used (no official VA). See Note when choosing Money out only.

4. Bank account is in the list

Open the webhook → Accounts tab.

All accounts mode: every account fires the webhook.

Specific accounts mode: the account producing the transaction must be in the Selected column.

New accounts are not auto-added

Accounts linked AFTER the webhook was created are not auto-added to the specific list. Open the webhook, add manually, save.

5. VA configuration

Each account in the list has 3 VA modes:

  • All VAs: receive every VA, including future ones.
  • Selected VAs only: only when the transaction goes through a ticked VA.
  • No VA: skip every VA transaction.

Transaction goes through a VA? Check the mode:

  • No VA: webhook skips, change the mode.
  • Selected VAs only: is that VA ticked?

Banks that only support VA (BIDV, MSB, KienlongBank, OCB) have no main account, a VA is required.

6. Payment code

If Only send when payment code exists is on, the transaction must have a recognizable code. See Payment code config for your company template.

If a Filter by payment code (prefix) is set, the code must start with one of the listed prefixes.

Quick test: turn this toggle off, retransfer the test amount.

7. What does the server return

Open Delivery logs → newest row → Response tab.

Returns HTTP 200/201 with body {"success": true} count as success. Anything else is marked failure even if the URL received the request. Details: Valid response.

8. Stuck in retry

If your server occasionally crashes or is slow, SePay is retrying. Each retry shows as its own row in Delivery logs.

Seeing 8 consecutive rows with the same id all Failed? The transaction is lost. Use Incidents to find all lost transactions and replay.

9. OAuth 2.0 auth failure

Webhooks using OAuth 2.0 require SePay to call the token endpoint first, then call the webhook URL with the Bearer token. If the token endpoint fails, the webhook URL receives nothing. When debugging don't only look at webhook logs.

OAuth 2.0 diagnostics

OAuth 2.0 can fail in 5 places. Open Delivery logs to identify:

WhereWhat you'll see in the logWhere to check
Cannot reach token endpointDNS Error / Timeout / Connection Refused on token fetchToken URL online? Firewall blocking SePay IPs?
Token endpoint returns wrong statusHTTP 4xx/5xx from token URLEndpoint must return 200
Token endpoint body wrong format"OAuth response in wrong format"Must have access_token (Standard) or data.accessToken (Custom)
Token OK, webhook URL returns 401Token acquired but webhook rejectsServer isn't verifying the Bearer correctly, or considers the token expired
Webhook URL returns 200SuccessNothing to do
Test the token endpoint with curl first

Before configuring the webhook, hit the token endpoint with curl:

Bash
1
2
3
curl -X POST https://your-auth-server/oauth/token \
-d "grant_type=client_credentials" \
-u "your_client_id:your_client_secret"

Response must be JSON status 200 with access_token. Once that works, configure the webhook.

OAuth config + flow: OAuth 2.0 Authentication.

FAQ

When to reconcile

Auto retries only run for about 33 minutes. If your endpoint is down longer, the webhook is lost. Use Reconciliation or Incidents to see what's missing and resend.

Next