Changelog & Migration Notes
A short, action-oriented summary of recent API behavior changes. Read the Action required sections if you have an existing integration — the rest is informational.TL;DR
- The system-internal
referencefield is no longer returned on payment detail / list responses. ReadclientReferenceinstead. Webhooks still send both keys (same value) for backward compatibility. - The
paidAtfield is now omitted from the verify response when the payment hasn’t been received yet (previously: empty string). - The hosted-checkout redirect URL now always includes both
paymentId(a real, verifiable payment ID) andreference(yourclientReference). - Underpayment protection on testnets now matches mainnet behavior —
underpaid testnet transactions reach you as
failedwith a populateddeficitAmountinstead of auto-completing.
Breaking changes
1. reference removed from payment detail and list responses
Internally we have two reference values per payment:
clientReference— the value you supplied (or one we generated for you if you didn’t pass one). Stable. Never mutated.reference(system-internal) — used as the idempotency key with our exchange providers. May be regenerated mid-lifecycle if an exchange order expires.
reference to merchants was misleading because
it could change. We’ve removed it from outbound API surfaces.
Affected endpoints:
GET /api/v1/payments/:paymentId—referenceis no longer in the response.GET /api/v1/payments(list) — each row no longer includesreference.
payment.reference from these responses, switch to
payment.clientReference. The value is the same as what you passed (or the
auto-generated one we use if you didn’t pass one).
2. paidAt is omitted (not empty) when unpaid
GET /api/v1/payments/:paymentId/verify previously returned "paidAt": ""
when the payment hadn’t been received. It now omits the field entirely.
Action required
If you parse paidAt directly as an ISO timestamp, handle the missing case:
3. List Payments response shape
TheGET /api/v1/payments (list) response was previously documented as
{ items, page, pageSize, totalItems, totalPages }. The actual response shape
is { data, currentPage, pageSize, totalItems, totalPages, hasNext, hasPrev }.
If you coded to the documented shape, your integration was already
broken — switch to the actual shape. If you coded to the actual response,
nothing to do.
Improvements (no action required)
Hosted-checkout redirect now always carries useful query params
When the hosted checkout redirects the customer back to yourcallbackURL
(success) or failureURL (failure), it now reliably appends:
paymentId— the real ChainPal payment ID (a 24-character ObjectID hex string). You can pass this directly toGET /api/v1/payments/:paymentId/verify.reference— yourclientReference.
paymentId could be the 12-character payCode (which the
verify endpoint rejects), and reference was sometimes empty if you hadn’t
passed one at initialization.
Now: both are guaranteed populated. If you weren’t using these query
params before, you can rely on them now.
clientReference is now always populated
If you don’t supply a reference when calling
POST /api/v1/payments, ChainPal generates one for you and stores it as the
payment’s clientReference. You’ll see this same value in:
- The
clientReferencefield of every API response that surfaces it. - The
referenceandclientReferencekeys in webhook payloads. - The
referencequery param appended to yourcallbackURL/failureURLredirects.
clientReference as the canonical,
stable, always-present payment identifier without having to fall back to
paymentId for unsupplied-reference cases.
failureURL is now exposed in the checkout DTO
If you set failureURL on payment initialization, the checkout page can now
read it back through the internal API. This means failure redirects will
actually fire — previously the field was ignored on the checkout page even
when you set it.
Webhooks: reference and clientReference carry the same value
Both keys in the webhook payload are now populated from your
clientReference. The reference key is kept as a legacy alias — new
integrations should prefer clientReference, but no urgent migration is
needed.
Test and live keys can now be used side-by-side
Previously a merchant account had a single “active” environment and the API rejected requests sent with a key whose prefix didn’t match. That gate is gone. Each request is routed by the prefix on the key you send (cp_*_test_* → test, cp_*_live_* → live), so you can run a staging
integration and a live integration concurrently without flipping a switch.
The deprecated POST /users/public-api/toggle-environment endpoint now
returns 410 Gone. See the
Authentication
page for the full picture.
Disabling the API in your dashboard preserves your keys
If a merchant disables the developer API in the dashboard, requests with existing keys start returning401 Unauthorized (as expected), but the
keys themselves are kept on file. Re-enabling resumes service without
forcing a key rotation. Your integration code does not need to handle a
“keys destroyed” case any more.
Testnet underpayment behavior now matches mainnet
Mainnet payments have always rejected materially-underpaid transactions via the exchange-provider settlement step. On testnets, the settlement step is skipped (no exchange provider in test mode), so underpaid testnet transactions previously auto-completed. Testnet now applies the same underpayment check as mainnet: an underpaid transaction is markedstatus: failed with a populated deficitAmount. If
your test integration relied on the old behavior of “any non-zero crypto
counts as paid”, update it to handle the realistic failed/deficitAmount
case — this matches what your live integration would have seen all along.
Quick checklist for existing integrations
- Replace any reads of
payment.referencewithpayment.clientReferenceonGET /payments/:idandGET /payments. - Treat the verify endpoint’s
paidAtas optional — guard before parsing as a date. - If you parsed list responses as
{ items, page }, switch to{ data, currentPage }. - (Optional) Start using
clientReferenceas your canonical payment identifier in webhook handlers and redirect-callback handlers.

