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:
| Attempt | Wait | Total |
|---|---|---|
| 1 (initial) | now | 0 min |
| 2 | 1 min | 1 min |
| 3 | 1 min | 2 min |
| 4 | 2 min | 4 min |
| 5 | 3 min | 7 min |
| 6 | 5 min | 12 min |
| 7 | 8 min | 20 min |
| 8 (last) | 13 min | 33 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.
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
| Error | What it is | How to fix |
|---|---|---|
| DNS Error | Domain not found | Check URL, DNS |
| Connection Refused | Server refused | Check server is running, port, firewall |
| Timeout | More than 30 seconds with no response | Optimize endpoint, return 200 first then process |
| SSL Error | Certificate problem | Check cert, CA chain, no self-signed |
HTTP errors
| Status | What it is | How to fix |
|---|---|---|
| 401 | Wrong auth | Check API Key, Secret Key |
| 403 | Blocked | Add SePay IPs to whitelist |
| 404 | Wrong URL | Check the path (case-sensitive) |
| 405 | Wrong method | Endpoint must accept POST |
| 500 | Server error | Check logs |
| 502/503 | Server not ready | Maintenance or overload |
Response speed
| Time | Note |
|---|---|
| < 1 second | Good |
| 1-5 seconds | Should improve |
| > 5 seconds | Likely to timeout |
cURL codes
The log records error_code (cURL code) + response_time_ms per delivery.
| Code | Error | Notes |
|---|---|---|
| 6 | DNS | Wrong domain or DNS not pointing |
| 7 | Connection Refused | Server off, wrong port, firewall block |
| 28 | Timeout | Response took more than 30 seconds |
| 35 | SSL | TLS handshake error |
| 51 | SSL Cert | Invalid certificate |
| 55 | Send Error | Connection lost while sending |
| 56 | Receive Error | Connection lost while receiving |
| 60 | CA Cert | Cannot 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.
| Result | Common cause |
|---|---|
| Success | URL is fine, go to step 3 |
| DNS Error | Domain not pointing or URL typo |
| Connection Refused | Server not listening on that port |
| Timeout | Firewall blocking or server too slow |
| SSL Error | Certificate expired or invalid |
| HTTP 4xx / 5xx | Server 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.
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:
| Where | What you'll see in the log | Where to check |
|---|---|---|
| Cannot reach token endpoint | DNS Error / Timeout / Connection Refused on token fetch | Token URL online? Firewall blocking SePay IPs? |
| Token endpoint returns wrong status | HTTP 4xx/5xx from token URL | Endpoint 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 401 | Token acquired but webhook rejects | Server isn't verifying the Bearer correctly, or considers the token expired |
| Webhook URL returns 200 | Success | Nothing to do |
Before configuring the webhook, hit the token endpoint with curl:
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
- Reconciliation: backup when webhooks are lost beyond 5 hours
- Monitoring: logs, alerts, incidents
- Integrate webhook: deduplication
- Security: hardening checklist