# Troubleshooting (/docs/direct-debit/troubleshooting)

## Authentication Issues [#authentication-issues]

### Invalid or Expired Token [#invalid-or-expired-token]

**Symptom:**`401 Unauthorized` response

**Causes:**

- Access token has expired (tokens are valid for 24 hours)
- Token not included in Authorization header
- Malformed Authorization header

**Solutions:**

1. Request a new access token:

```bash
curl -X POST https://dev-payce.auth.eu-west-1.amazoncognito.com/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
  -d "grant_type=client_credentials"
```

1. Ensure correct header format:

```text
Authorization: Bearer eyJraWQiOiJ...
```

1. Implement token refresh logic before expiry

### Invalid Client Credentials [#invalid-client-credentials]

**Symptom:**`invalid_client` error from the authentication endpoint

**Causes:**

- Incorrect client_id or client_secret
- Using sandbox credentials in production or vice versa
- Credentials not Base64 encoded correctly

**Solutions:**

1. Verify credentials match environment (sandbox vs production)
2. Check for extra whitespace in credentials
3. Ensure Basic auth header is properly formatted:

```bash
# Correct format
-u "client_id:client_secret"

# Or manually encode
echo -n "client_id:client_secret" | base64
```

## Mandate Creation Issues [#mandate-creation-issues]

### Bank Account Verification Failure [#bank-account-verification-failure]

**Symptom:**`400 Bad Request` with verification failure details

**Example Error:**

```json
{
  "Message": "Debtor account verification failure: details={\"AccountExists\":true,\"AccountIsOpen\":false,...}"
}
```

**Causes:**

- Account is closed or dormant
- Incorrect account number or branch code
- Account type mismatch
- ID number doesn't match the account holder

**Solutions:**

| Verification Field | Resolution |
|---|---|
| `AccountExists: false` | Verify account number with customer |
| `AccountIsOpen: false` | Customer needs to reactivate or provide different account |
| `AccountTypeMatches: false` | Correct the `accountType` field (CURRENT/SAVINGS/TRANSMISSION) |
| `AccountAcceptsDebits: false` | Account cannot receive debits, use different account |
| `IdNumberMatches: false` | Verify ID number matches primary account holder |

<Callout type="warning">
**Workaround:** Set `verifyAccount: false` to skip verification (not recommended for production)
</Callout>

### Invalid Bank Name Format [#invalid-bank-name-format]

**Symptom:**`400 Bad Request` mentioning the bank field

**Cause:** Bank name not in the correct format

**Solution:** Use uppercase, single-word format:

| Incorrect | Correct |
|---|---|
| `Standard Bank` | `STANDARDBANK` |
| `Absa` | `ABSA` |
| `First National Bank` | `FNB` |
| `capitec` | `CAPITEC` |

### Duplicate External Reference [#duplicate-external-reference]

**Symptom:**`409 Conflict` error

**Cause:** The `externalReference` value already exists

**Solutions:**

1. Use unique references for each mandate/collection
2. Implement UUID generation:

```jsx
const externalReference = crypto.randomUUID();
```

1. Include timestamp or sequence number:

```jsx
const externalReference = `MAN-${Date.now()}-${sequenceNumber}`;
```

### DebiCheck Authentication Timeout [#debicheck-authentication-timeout]

**Symptom:** Mandate status remains `PENDING_AUTHENTICATION` then becomes `AUTH_FAILURE`

**Causes:**

- Customer didn't respond within 120 seconds (TT1)
- The customer's phone is off or has no signal
- The bank couldn't deliver USSD/push notification

**Solutions:**

1. Implement fallback authentication:

```json
{
  "authenticationType": "REALTIME",
  "fallbackAuthenticationType": "DELAYED_WITHOUT_AUTH"
}
```

1. Use delayed authentication for after-hours:

```json
{
  "authenticationType": "DELAYED"
}
```

1. Retry with batch processing:

```json
{
  "authenticationType": "BATCH"
}
```

1. Follow up with the customer to ensure they're expecting the authentication request

### DebiCheck Mandate Rejected [#debicheck-mandate-rejected]

**Symptom:** Status `AUTH_FAILURE` with rejection reason

**Causes:**

- The customer actively rejected the mandate
- Mandate details don't match customer expectations
- The customer doesn't recognise the merchant

**Solutions:**

1. Verify contract details with the customer before submitting
2. Ensure the abbreviated name is recognisable to the customer
3. Confirm collection amount and frequency
4. Re-submit with corrected details after customer confirmation

## Collection Issues [#collection-issues]

### Collection Returned Unpaid [#collection-returned-unpaid]

**Symptom:** Collection status `UNPAID`

**Common Causes and Solutions:**

| Reason | Solution |
|---|---|
| Insufficient funds | Wait for next mandated date to retry |
| Account closed | Contact customer for new account details |
| Payment stopped | Contact customer, may need new mandate |
| Not provided for | After 2 consecutive failures, obtain new mandate |

**PASA Rules:**

- Cannot resubmit until the next mandated date
- After 2 "Not provided for" returns, a new mandate is required
- "Account Closed" or "Payment Stopped" cannot be resubmitted

### Collection Disputed [#collection-disputed]

**Symptom:** Collection status `DISPUTED`

**Causes:**

- Customer claims no valid mandate exists
- Collection violates the mandate terms
- The customer cancelled the mandate

**Solutions:**

1. Retrieve and review the original mandate
2. Verify collection was within mandate parameters:

- Correct amount (within max limits)
- Correct collection day
- The mandate was active

1. Respond to the bank within 30 days with the mandate evidence
2. For DebiCheck: Disputes are less likely if the collection matches the authenticated terms

### Collection Submitted After Cut-off [#collection-submitted-after-cut-off]

**Symptom:** Collection not processed on the expected date

**Cause:** Submitted after bank processing cut-off time

**Cut-off Times:**

| Service | Weekday | Weekend |
|---|---|---|
| Same-day | 16:20 | 09:45 |
| One-day | 16:00 | 10:15 |
| Two-day | 14:30 (2 days prior) | 09:00 |
| DebiCheck | 16:00 (2 days prior) | 11:00 (2 days prior) |

**Solutions:**

1. Submit collections well before cut-off times
2. Account for your system processing time
3. For time-sensitive collections, use same-day service with early submission

## Payment Issues [#payment-issues]

### Payment Exceeds Limits [#payment-exceeds-limits]

**Symptom:** Payment rejected with "exceeds industry-imposed limits"

**Cause:** Payment amount ≥ R5,000,000

**Solution:** Route high-value payments through a real-time line (contact Precium support)

### Payment to an Invalid Account [#payment-to-an-invalid-account]

**Symptom:** Payment status `FAILED`

**Causes:**

- Invalid account number
- Incorrect branch code
- Account doesn't accept credits

**Solutions:**

1. Verify recipient account details
2. Consider implementing account verification before payment
3. Confirm the branch code matches the account's bank

## Webhook Issues [#webhook-issues]

### Not Receiving Webhooks [#not-receiving-webhooks]

**Symptom:** No callbacks received at registered URL

**Causes:**

- Callback URL not registered
- URL not publicly accessible
- HTTPS certificate issues
- Firewall blocking requests

**Solutions:**

1. Verify the callback URL is registered:

```bash
# Register callback URL
curl -X POST https://dev.api.revio.co.za/v2/clientcallbackurl/ \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{
    "clientId": YOUR_CLIENT_ID,
    "url": "https://your-server.com/webhook",
    "callbackType": "EFT_COLLECTION",
    "callbackVersion": "V2"
  }'
```

1. Test with webhook.site first:

```json
{
  "url": "https://webhook.site/your-unique-id"
}
```

1. Ensure your server:

- Is publicly accessible
- Has valid SSL certificate
- Responds within 5 seconds
- Returns HTTP 200

### Webhook Timeout Errors [#webhook-timeout-errors]

**Symptom:** Callbacks received but marked as failed

**Cause:** Webhook handler takes too long to respond

**Solution:** Return 200 immediately, process asynchronously:

```jsx
app.post('/webhook', (req, res) => {
  // Respond immediately
  res.status(200).send('OK');

  // Process in background
  processWebhookAsync(req.body);
});
```

## API Request Issues [#api-request-issues]

### 400 Bad Request [#400-bad-request]

**Symptom:** Request rejected with validation error

**Common Causes:**

1. **Missing required fields**

- Check all required fields are present
- Verify field names match API specification (case-sensitive)

1. **Invalid date format**

- Use ISO 8601 format: `2024-01-15T10:00:00.000Z`

1. **Invalid phone number format**

- Use format: `+27-XXXXXXXXX`

1. **Invalid amount**

- Amount must be in cents (integer)
- `10000` = R100.00

1. **Invalid enum values**

- Check accepted values for fields like `accountType`, `serviceType`, `frequency`

### 404 Not Found [#404-not-found]

**Symptom:** Resource not found error

**Causes:**

- Invalid resource ID
- Resource was deleted
- Wrong endpoint URL

**Solutions:**

1. Verify the ID exists and is correctly formatted (UUID)
2. Check endpoint URL matches documentation
3. Ensure you're using the correct API version (`/v2/`)

### 500 Internal Server Error [#500-internal-server-error]

**Symptom:** Server error response

**Actions:**

1. Note the timestamp and request details
2. Check for `x-request-id` in response headers
3. Retry with exponential backoff
4. If persistent, contact Precium support with:

- Request endpoint
- Request payload (redact sensitive data)
- Timestamp
- Request ID (if available)

## Environment Issues [#environment-issues]

### Using the Wrong Environment [#using-the-wrong-environment]

**Symptom:** Unexpected behaviour, missing data, or authentication failures

**Cause:** Mixing sandbox and production credentials/endpoints

**Environment Reference:**

| Environment | Auth Endpoint | API Endpoint |
|---|---|---|
| Sandbox | `dev-payce.auth.eu-west-1.amazoncognito.com` | `dev.api.revio.co.za` |
| Production | `prod-payce.auth.eu-west-1.amazoncognito.com` | (provided at go-live) |

**Solution:** Use environment variables to manage configuration:

```jsx
const config = {
  authUrl: process.env.REVIO_AUTH_URL,
  apiUrl: process.env.REVIO_API_URL,
  clientId: process.env.REVIO_CLIENT_ID,
  clientSecret: process.env.REVIO_CLIENT_SECRET
};
```

## Getting Help [#getting-help]

If you cannot resolve an issue:

1. **Check documentation:** Review the relevant section of this documentation
2. **Review Swagger:** https://dev.api.revio.co.za/swagger/index.html
3. **Test in sandbox:** Reproduce the issue in the sandbox environment
4. **Contact support:** Email support@precium.com with:

- Description of the issue
- Steps to reproduce
- Request/response examples (redact sensitive data)
- Environment (sandbox/production)
- Timestamp of failed requests

*© 2026 Precium. All rights reserved.*
