Template errors
Why a template ends failed or discarded, and what each state reason means.
A template carries state_reason in its two terminal states, and they mean very different things:
failed— the template never became payable. Its payment-token setup exhausted its retries, so no payer could ever reach it.discarded— the template was payable and its life ended: it was pruned or deleted, and its payment tokens were torn down.
You read the code in the payin_template_failed and payin_template_discarded webhooks, and on the template itself via get or list.
Example webhook payload
{
"timestamp": "2026-02-11T14:22:07.913204Z",
"event": {
"type": "payin_template_failed",
"data": {
"id": "cpit_034EASMHlq36oZISbc9W4b",
"state": "failed",
"state_reason": "key_already_registered",
"external_id": "campaign-2026-02",
"subaccount_id": "csub_034EASMIAFVq06j2NxhnIJ"
}
}
}Failed — the template never became payable
A template is created in created and only reaches ready once its payment token exists. These are the ways that setup ends instead.
| Code | Description | Cause | Suggested fix |
|---|---|---|---|
key_already_registered | Key already taken | The custom key value you asked for is already registered in the instant payments network — keys are unique across the whole network. | Create the template again with a different custom_key_value, or let Mono generate the key. |
key_registration_failed | Key registration failed | Registering the payment key with the network exhausted its retries. | Create the template again. If it repeats, contact support with the template id. |
collection_registration_failed | Collection registration failed | Mono could not register the collection with the payment method's provider. | Create the template again. If it repeats, contact support with the template id. |
unknown | Unknown | An unclassified failure during setup. | Contact support with the template id. |
A failed template is final, but its `external_id` is not
There is no way to retry the setup of a template that already failed — the state is terminal.
Create a new one instead: a template that ends failed or discarded releases its
external_id, so you can reuse the same identifier for the replacement.
Discarded — the template's life ended
Three ways out, and only one of them is something you asked for. All three tear down the payment tokens, so any key or QR already distributed stops working.
| Code | Description | Cause | Suggested fix |
|---|---|---|---|
deleted | Deleted | You deleted the template through the API. | Expected. Create a new template when you need to collect again. |
expired | Expired | The template passed its expires_at and was pruned. | Set a longer window on the next one, or create a new template. |
inactivity | Inactivity | The template went too long without activity and was pruned. It covers both cases — never paid, and paid at some point but idle since. | Create a new template. If the collection is long-lived, keep it in use or reissue it. |
`prune_reason` says more than `state_reason`
For a pruned template, prune_reason keeps the finer distinction that state_reason
collapses: inactivity_without_attempts and inactivity_with_attempts both become
inactivity. Read prune_reason when you need to tell "nobody ever paid" from "payments
stopped" — see Pruning.
Handling them
A template that fails or is discarded is not an error in a payment — it is a collection that no longer exists. What matters is that you stop distributing its payment token, because payers holding a key or QR from it will have their payments refused with template_deleted, template_expired or template_invalid_state.
app.post('/webhook', (req, res) => {
const { event } = req.body;
if (event.type === 'payin_template_failed') {
const { id, state_reason } = event.data;
if (state_reason === 'key_already_registered') {
// Pick another custom key value and create the template again
} else {
// Setup failed: retry creation, and alert if it repeats
}
}
if (event.type === 'payin_template_discarded') {
const { id, state_reason } = event.data;
// Stop showing this template's key or QR to payers
// `deleted` is yours; `expired` and `inactivity` are the sweep
}
res.status(200).send();
});Support
When contacting support about a template, provide the template id and the external_id you
created it with.
Next steps
- Payin errors — why an individual payment was refused.
- Pruning — when and why templates are swept.
- Webhooks — every template event and its payload.