Enroll merchants in alerts
Enroll a merchant's billing descriptor in Ethoca, CDRN, RDR and TC40 alert programs with API v3, track each program until alerts flow, enroll in bulk, and disenroll. Early access for invited platforms.
You are a platform that wants your merchants covered by pre-chargeback alerts without sending them to a dashboard. This guide takes one merchant from no enrollment to alerts flowing, using the alert enrollment endpoints in API v3.
API v3 is in early access
API v3 is in early access. Alert enrollments are the first API v3 capability open for use, and only to invited platforms. API v3 is not generally available: every other part of your integration stays on the 2025-04-01 API. To get access for your platform, contact your Chargeflow account manager through Support.
What you'll build
An alert enrollment covers one billing descriptor on one processor for one merchant. Each enrollment carries one or more alert programs, and every program confirms on its own schedule, so you track them separately.
| Program | Network | Needs |
|---|---|---|
ethoca | Ethoca alerts (Mastercard) | psp and descriptor |
cdrn | Verifi CDRN | psp and descriptor |
rdr | Verifi Rapid Dispute Resolution | psp, descriptor, card_acceptor_id, bin, at least one arns |
tc40 | Visa TC40 fraud reports | psp, descriptor, card_acceptor_id, bin, at least one arns |
RDR refunds automatically
RDR refunds automatically. It resolves a matching dispute inside the card network by refunding
the cardholder before it becomes a chargeback. Neither you nor Chargeflow can decline that refund
per alert. Leave rdr out unless the merchant accepts automatic refunds.
How alerts deflect chargebacks is covered in Alerts model.
Prerequisites
- A platform API key with v3 access, shaped
cf_live_<key_id>_<secret>. v3 sends it asAuthorization: Bearer, not in thex-api-keyheader the 2025-04-01 API uses. A merchant key is refused with403 WRONG_KEY_TYPE. - The merchant's
chargeflow_id, the 24-character account ID of a merchant in your portfolio. - The merchant onboarded with Alerts. v3 has no endpoint for this step. Step 1 tells you whether it is done.
- The billing descriptor exactly as it appears on the cardholder's statement, and the processor it bills through.
The snippets read the key from CHARGEFLOW_PLATFORM_API_KEY:
export CHARGEFLOW_PLATFORM_API_KEY="YOUR_PLATFORM_API_KEY"Check the merchant's Alerts account
Confirm the key works and the merchant is ready before you enroll anything.
curl https://api.chargeflow.io/v3/merchants/00000000d0c5000000000001/alert_account \
-H "Authorization: Bearer $CHARGEFLOW_PLATFORM_API_KEY"You should see a 200 with the merchant's Alerts settings and a count of its programs by status:
{
"object": "alert_account",
"chargeflow_id": "00000000d0c5000000000001",
"created_at": "2026-08-12T09:30:00Z",
"settings": {
"alerts_only": false,
"manual_billing": false,
"transaction_linking_disabled": false,
"payment_method_authorized": true
},
"payment_method_authorized_at": "2026-08-12T09:31:00Z",
"last_disenrollment_requested_at": null,
"enrollment_count": 0,
"provider_status_counts": {
"in_progress": 0,
"enrolled": 0,
"active": 0,
"in_process_of_disenrollment": 0,
"disenrolled": 0,
"dropped": 0
},
"livemode": true
}If this fails
404 ALERT_ACCOUNT_NOT_FOUND means the merchant was never onboarded with Alerts: ask your
Chargeflow account manager to onboard it, then retry. 403 MERCHANT_NOT_IN_PORTFOLIO means the
chargeflow_id is not one of your merchants. 401 UNAUTHORIZED means the key or the Bearer
prefix is wrong.
Create the enrollment
Send the processor, the descriptor and the programs. providers defaults to ["ethoca"] when you leave it out.
curl -X POST https://api.chargeflow.io/v3/alert_enrollments \
-H "Authorization: Bearer $CHARGEFLOW_PLATFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"chargeflow_id": "00000000d0c5000000000001",
"psp": "stripe",
"descriptor": "EXAMPLE*STORE",
"providers": ["ethoca", "cdrn"]
}'You should see a 202: the request was accepted and each program starts in_progress. Store the id, the next steps use it.
{
"id": "aen_00000000d0c5000000000001-00000000-d0c5-4000-8000-000000000003",
"object": "alert_enrollment",
"created_at": "2026-08-12T09:30:00Z",
"updated_at": "2026-08-12T09:30:00Z",
"livemode": true,
"psp": "stripe",
"chargeflow_id": "00000000d0c5000000000001",
"descriptor": "EXAMPLE*STORE",
"providers": [
{
"provider": "ethoca",
"status": "in_progress",
"status_reason": "sent_to_enrollment",
"retryable": null,
"enrolled_at": null,
"alerts_stopped_at": null,
"updated_at": "2026-08-12T09:30:00Z"
},
{
"provider": "cdrn",
"status": "in_progress",
"status_reason": "sent_to_enrollment",
"retryable": null,
"enrolled_at": null,
"alerts_stopped_at": null,
"updated_at": "2026-08-12T09:30:00Z"
}
],
"card_acceptor_id": null,
"bin": null,
"arns": []
}Creating is safe to repeat. The same psp and descriptor (compared ignoring case and extra spaces) always resolve to the same enrollment: a repeat turns on any program that is off, never stops one that is on, and answers 200 when nothing changed. Retry a 502 or 504 without fear of a duplicate.
If this fails
A 422 means the request was well formed but refused, and nothing was created. The usual codes:
MISSING_FIELD (rdr or tc40 without card_acceptor_id, bin and arns, so send all three
together), UNKNOWN_PSP (the processor is not supported), and DESCRIPTOR_UNAVAILABLE (this
descriptor cannot be enrolled on this processor). The full list is on Create an alert
enrollment.
Subscribe to status changes
Programs confirm asynchronously. Register a webhook for alerts.enrollment.status_changed so you hear about each change without polling.
curl -X POST https://api.chargeflow.io/v3/webhooks/registrations \
-H "Authorization: Bearer $CHARGEFLOW_PLATFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url_target": "https://example.com/hooks/chargeflow",
"event_types": ["alerts.enrollment.status_changed"]
}'You should see a 201. signing_secret appears in this response and never again, so store it now: you need it to verify the X-Chargeflow-Signature header on every delivery.
{
"id": "whr_d0c5000001",
"object": "webhook_registration",
"url_target": "https://example.com/hooks/chargeflow",
"event_types": ["alerts.enrollment.status_changed"],
"is_active": true,
"created_at": "2026-08-12T09:32:00.000Z",
"updated_at": "2026-08-12T09:32:00.000Z",
"livemode": true,
"signing_secret": "YOUR_SIGNING_SECRET"
}If this fails
409 WEBHOOK_ALREADY_EXISTS means this URL is already registered: one registration per URL. Add
the event type to the existing one with Update a
registration.
Signature verification and the retry schedule are on Create a registration.
Wait for active
Each time a program changes status, your endpoint receives one event for that program:
{
"webhookId": "whd_d0c5000001",
"eventId": "evt_d0c5000001",
"creationDate": "2026-08-13T08:00:03.000Z",
"type": "alerts.enrollment.status_changed",
"livemode": true,
"data": {
"enrollment_id": "aen_00000000d0c5000000000001-00000000-d0c5-4000-8000-000000000003",
"chargeflow_id": "00000000d0c5000000000001",
"descriptor": "EXAMPLE*STORE",
"psp": "stripe",
"provider": "ethoca",
"status": "enrolled",
"status_reason": null,
"occurred_at": "2026-08-13T08:00:02.381Z"
}
}Return a 200 within 10 seconds. Events arrive at least once and not always in order, so deduplicate on eventId and ignore an event whose occurred_at is older than what you already hold for that enrollment_id and provider.
enrolled means the network confirmed, not that alerts are arriving. The merchant is covered by a program only when that program reaches active.
Verify it worked
Retrieve the enrollment and read each program's status. This is also the fallback when a webhook is missed.
curl https://api.chargeflow.io/v3/alert_enrollments/aen_00000000d0c5000000000001-00000000-d0c5-4000-8000-000000000003 \
-H "Authorization: Bearer $CHARGEFLOW_PLATFORM_API_KEY"You should see the same object the create returned, with each entry in providers at its current status. To see every enrollment for a merchant, use List alert enrollments with chargeflow_id.
Read the status
Every program on an enrollment has its own status:
| Status | Meaning | What to do |
|---|---|---|
in_progress | Submitted and not yet confirmed. Also a refused program: see the next section. | Wait, or act on status_reason |
enrolled | The network confirmed. No alert has arrived yet. | Wait |
active | Alerts are flowing. The only value that means this. | Nothing, the merchant is covered |
in_process_of_disenrollment | Removal is in progress. Still billable, and alerts can still arrive. | Wait |
disenrolled | Removed. | Create again to re-enroll |
dropped | Never went live and was closed after 45 days. | Read status_reason for the cause |
When a program is refused
A refused program does not get its own status: it stays in_progress with an error in status_reason and a retryable flag. Branch on retryable.
retryable | status_reason | What to do |
|---|---|---|
false | generic_descriptor, invalid_arn, invalid_caid_bin, vendor_submission_rejected | It can't succeed as it is. Enroll a corrected descriptor or identifiers. |
true | additional_descriptors_required, requires_migration, payment_method_issue | Resolve the cause named. The enrollment can then succeed unchanged. |
true | vendor_submission_failed, technical_failure | Nothing. Chargeflow follows up. |
null | sent_to_enrollment and other non-error reasons | Informational. Keep waiting. |
Every reason and its meaning is listed on Retrieve an alert enrollment.
Enroll many descriptors at once
A merchant with several descriptors can be enrolled in one call: up to 25 items, all for the same merchant. Each item takes the same fields as a single create, without chargeflow_id.
curl -X POST https://api.chargeflow.io/v3/alert_enrollments/bulk \
-H "Authorization: Bearer $CHARGEFLOW_PLATFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"chargeflow_id": "00000000d0c5000000000001",
"enrollments": [
{ "psp": "stripe", "descriptor": "EXAMPLE*STORE" },
{ "psp": "adyen", "descriptor": "EXAMPLE*STORE EU", "providers": ["ethoca", "rdr"] }
]
}'You should see a 207 with one result per item, in request order. A 207 does not mean every item succeeded: check each item's status. A 200 or 202 carries alert_enrollment, anything else carries error.
{
"results": [
{
"index": 0,
"status": 200,
"alert_enrollment": {
"id": "aen_00000000d0c5000000000001-00000000-d0c5-4000-8000-000000000003",
"object": "alert_enrollment",
"created_at": "2026-08-12T09:30:00Z",
"updated_at": "2026-08-12T09:30:00Z",
"livemode": true,
"psp": "stripe",
"chargeflow_id": "00000000d0c5000000000001",
"descriptor": "EXAMPLE*STORE",
"providers": [
{
"provider": "ethoca",
"status": "in_progress",
"status_reason": "sent_to_enrollment",
"retryable": null,
"enrolled_at": null,
"alerts_stopped_at": null,
"updated_at": "2026-08-12T09:30:00Z"
}
],
"card_acceptor_id": null,
"bin": null,
"arns": []
}
},
{
"index": 1,
"status": 422,
"error": {
"type": "validation_error",
"code": "MISSING_FIELD",
"message": "card_acceptor_id is required for rdr.",
"field": "card_acceptor_id",
"request_id": "00000000-d0c5-4000-8000-000000000004"
}
}
]
}Resend only the items that failed with 500, 502 or 504: a retry is safe. A 422 item needs a fix first.
Stop an enrollment
Disenroll some programs by naming them in providers, or all of them by sending no body.
curl -X POST https://api.chargeflow.io/v3/alert_enrollments/aen_00000000d0c5000000000001-00000000-d0c5-4000-8000-000000000003/disenrollments \
-H "Authorization: Bearer $CHARGEFLOW_PLATFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "providers": ["cdrn"], "reason": "Merchant request" }'You should see a 202 with the enrollment, and the named program at in_process_of_disenrollment. It moves to disenrolled once the network confirms, which can take days. Until then the program is still billable and alerts can keep arriving, so keep handling them. The enrollment stays readable at the same id, and creating it again re-enrolls it.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
401 UNAUTHORIZED | The key is missing, malformed, revoked, or sent in x-api-key | Send Authorization: Bearer with a cf_live_ key |
403 WRONG_KEY_TYPE | A merchant key called an Alerts endpoint | Use your platform key |
403 MERCHANT_NOT_IN_PORTFOLIO | The chargeflow_id is not one of your merchants | Check the ID. The answer is the same when the merchant does not exist |
404 ALERT_ACCOUNT_NOT_FOUND | The merchant was never onboarded with Alerts | Ask your account manager to onboard it |
422 ENROLLMENT_IDENTIFIERS_DIFFER | The enrollment exists with a different card_acceptor_id, bin or arns | Leave the field out to keep the stored value. resource_id is the existing enrollment |
422 PROVIDER_DISENROLLING | The program is still in_process_of_disenrollment | Create again once it is disenrolled |
422 PROVIDER_NOT_ENROLLED | A disenroll named a program the enrollment never had | Name only programs listed in the enrollment's providers |
429 RATE_LIMITED | Too many requests | Wait the seconds in Retry-After |
A program stays in_progress | Still waiting on the network, or refused | Read status_reason and retryable: see When a program is refused |
A program is enrolled, no alerts | Confirmed, not yet flowing | Wait for active |
Error bodies, and which statuses are safe to retry, are on v3 errors.