Platform
Testing & Debugging
Validate your integration end-to-end using test mode, synthetic card numbers, and simulated error amounts — all without touching real money.
Test mode vs Live mode
Every PaymentHooks account operates in two isolated environments. Test mode lets you exercise the full API without moving real funds. Live mode processes actual charges through your configured payment gateways.
| Property | Test mode | Live mode |
|---|---|---|
| Secret key prefix | sk_test_… | sk_live_… |
| Public key prefix | pk_test_… | pk_live_… |
| Real charges | No | Yes |
| Webhook deliveries | Yes | Yes |
| Dashboard toggle | Test mode banner visible | No banner |
Objects created in test mode are never visible in live mode and vice versa. Payment IDs, customer records, webhooks, and logs are fully segregated.
Test card numbers
Use these card numbers when submitting payments in test mode. Combine them with any valid future expiry date and the appropriate CVC length.
| Card number | Brand | Outcome | CVC |
|---|---|---|---|
| 4242 4242 4242 4242 | Visa | Always succeeds | Any 3-digit |
| 4000 0000 0000 0002 | Visa | Always declines | Any 3-digit |
| 5555 5555 5555 4444 | Mastercard | Succeeds | Any 3-digit |
| 4000 0000 0000 9995 | Visa | Insufficient funds | Any 3-digit |
| 4000 0000 0000 9979 | Visa | Stolen card | Any 3-digit |
| 4000 0000 0000 3220 | Visa | 3D Secure required | Any 3-digit |
| 4000 0000 0000 0077 | Visa | Succeeds, then disputes | Any 3-digit |
| 3782 822463 10005 | Amex | Always succeeds | Any 4-digit |
Test webhook events
You can trigger synthetic webhook events directly from the dashboard without creating a real payment object:
- 1Navigate to Developers → Webhooks in your merchant dashboard.
- 2Select the endpoint you want to target.
- 3Click Send test event and choose an event type from the dropdown.
- 4Inspect the delivery log to confirm receipt and view the response your server returned.
Every webhook delivery follows this envelope structure:
{"id": "evt_01HXYZ123456","type": "payment.succeeded","created": 1716820000,"livemode": false,"api_version": "2024-01","data": {"object": {"id": "pay_01HXYZ789","amount": 4999,"currency": "usd","status": "succeeded"}}}
Simulating errors
To trigger a specific decline code without depending on a particular card number, create a payment with an amount whose last two digits match one of the codes below. This works with any test card number that would otherwise succeed.
| Amount ending in | Error code | Description | Example (USD) |
|---|---|---|---|
| …01 | card_declined | Generic card decline | ${(101 / 100).toFixed(2)} → e.g. $1.01 |
| …02 | expired_card | Card expiry date has passed | ${(102 / 100).toFixed(2)} → e.g. $1.02 |
| …03 | processing_error | Issuer processing failure | ${(103 / 100).toFixed(2)} → e.g. $1.03 |
| …05 | insufficient_funds | Insufficient funds on card | ${(105 / 100).toFixed(2)} → e.g. $1.05 |
Debugging checklist
If your integration is not behaving as expected, work through the following common causes before opening a support ticket.
API key not set / wrong mode
Verify PAYMENTHOOKS_SECRET_KEY is set in your environment and uses the sk_test_ prefix in test mode.
Missing required parameters
Check the error response for the param field. Common omissions: currency, amount, or payment_method.
Webhook signature verification failures
Ensure you are passing the raw request body (before JSON parsing) to verifyWebhookSignature, and that the correct WEBHOOK_SECRET is in use.
Currency mismatch
Currency codes must be lowercase ISO 4217 strings (e.g. usd, eur, gbp). Uppercase values are rejected.
Idempotency key conflicts
A reused Idempotency-Key with different parameters returns a 409 Conflict. Generate a fresh key per unique request.
Error response format
All API errors return a consistent JSON envelope alongside the appropriate HTTP status code (4xx or 5xx). The structure is:
{"error": {"type": "card_error","code": "card_declined","decline_code": "insufficient_funds","message": "Your card has insufficient funds.","param": "payment_method","request_id": "req_01HXYZ999","doc_url": "https://paymenthooks.com/docs/errors#card_declined"}}
| Field | Type | Description |
|---|---|---|
| type | string | High-level error category: card_error, api_error, validation_error, or authentication_error. |
| code | string | Machine-readable error code. Use this for programmatic handling. |
| decline_code | string | null | Issuer-specific decline reason, present only for card_error responses. |
| message | string | Human-readable explanation suitable for logging. Do not display this verbatim to end users. |
| param | string | null | The request parameter that caused the error, when applicable. |
| request_id | string | Unique identifier for this API request. Provide this to support when reporting an issue. |
| doc_url | string | null | A direct link to relevant documentation for this error code. |