Debugging integrations
Tools and techniques to debug your VINR integration.
When a charge fails, a webhook never lands, or a subscription drifts out of sync, the fastest path to a fix is reading what VINR actually saw. This page covers the request log, webhook delivery inspection, idempotency replays, and a repeatable sandbox workflow so you can reproduce and resolve issues without guessing.
Start with the request IDAsk
Every API response carries a request ID in the x-vinr-request-id header, and every error body echoes it. Capture and log it on the client side — it is the single fastest way to find the corresponding entry in the Dashboard or to hand to support.
import { Vinr } from '@vinr/sdk';
const vinr = new Vinr({ secretKey: process.env.VINR_SECRET_KEY });
try {
const payment = await vinr.payments.create({
amount: 1000,
currency: 'EUR',
customer: 'cust_8sQ2',
});
console.log('ok', payment.id);
} catch (err) {
// VinrError exposes the request ID, HTTP status, and a stable code.
console.error('vinr error', {
requestId: err.requestId,
status: err.status,
code: err.code, // e.g. card_declined, idempotency_conflict
message: err.message,
});
}Codes (err.code) are stable across versions and safe to branch on. The human-readable message is for logs and is not a contract — never parse it.
Request logsAsk
The Dashboard logs every API request against your account: method, path, status, latency, the request ID, and the redacted request/response bodies. Filter by status code to surface failures, or paste a request ID straight into the search box.
You can also pull the same data programmatically to wire it into your own observability:
curl https://api.vinr.com/v1/request_logs?status=4xx \
-H "X-Api-Key: $VINR_SECRET_KEY"| Filter | Use it to find |
|---|---|
status=4xx | Bad input, auth failures, idempotency conflicts |
status=5xx | Transient VINR-side errors worth retrying |
resource=payment | Every call touching a specific resource type |
request_id=req_… | A single exact request |
Logs in the live account are retained for 30 days; sandbox logs for 7. Export anything you need to keep beyond that window.
Inspecting webhook deliveryAsk
Most "the event never arrived" reports are delivery failures you can see and replay. Open the webhook endpoint in the Dashboard to view every attempt, the HTTP status your server returned, the response body, and the next scheduled retry.
Confirm the event was generated
Find the resource (for example pay_…) in its log and check that the expected transition happened at all. A payment stuck in requires_authentication never fires payment.completed.
Check the delivery attempts
A non-2xx response or a timeout (over 10 seconds) counts as a failure. VINR retries with exponential backoff for up to 72 hours.
Verify the signature locally
A silent 400 from your own handler is the most common cause. Confirm you pass the raw request body — not a parsed object — to the verifier.
Replay the delivery
Once your endpoint is fixed, click Resend on any past attempt, or replay from code (see below).
// The body MUST be the raw bytes. Re-serializing JSON changes the
// signature and verification will fail.
export async function POST(req: Request) {
try {
const event = vinr.webhooks.verify(
await req.text(),
req.headers.get('x-vinr-signature'),
);
console.log('verified', event.id, event.type);
return new Response('OK', { status: 200 });
} catch (err) {
console.error('signature verification failed', err.message);
return new Response('bad signature', { status: 400 });
}
}Idempotency replaysAsk
When you send an idempotency key, VINR returns the original stored response for any retry with the same key. That makes debugging deterministic: replay the exact request and compare. A response carrying x-vinr-idempotent-replay: true means you hit the cache, not a fresh execution.
curl https://sandbox.api.vinr.com/v1/payments \
-H "X-Api-Key: $VINR_SECRET_KEY" \
-H "Idempotency-Key: order-4711-attempt-1" \
-H "Content-Type: application/json" \
-d '{"amount":1000,"currency":"EUR","customer":"cust_8sQ2"}'An idempotency_conflict error means you reused a key with a different body. Generate a fresh key per logical operation; do not recycle keys across distinct requests.
Common pitfallsAsk
Reproducing in sandboxAsk
The sandbox mirrors live behavior with deterministic test cards, so you can trigger any branch on demand.
| Card number | Outcome |
|---|---|
| 4242 4242 4242 4242 | Succeeds → payment.completed |
| 4000 0000 0000 0002 | Declined → payment.failed (card_declined) |
| 4000 0000 0000 3220 | Triggers 3DS → payment.requires_authentication |
To debug webhooks without a public URL, run the CLI to forward live deliveries to localhost:
vinr listen --forward-to http://localhost:3000/api/webhooksThe CLI prints the signing secret for the forwarding session and logs every event it relays, so you can step through handlers in your debugger.
Next stepsAsk
Webhooks
Receive, verify, and retry events reliably.
Idempotency
Make every request safe to retry.
Error reference
Every error code and how to handle it.
Last updated on