Payment follow-ups that stop after payment succeeds
Use delayed HTTP work to revisit an invoice without treating an old payment event as the customer's current status.
A failed-payment event describes something that happened. It does not prove the invoice will still be unpaid tomorrow. A useful follow-up system keeps that difference explicit: the delayed job asks your application to check the invoice again.
Make eligibility a product rule
Write down which invoices qualify for a follow-up, how long to wait, and which states end the sequence. Paid, voided and disputed invoices may need different treatment. These are decisions for your product and billing process, not conditions a generic HTTP scheduler can infer.
Store a follow-up record with the invoice identifier, the intended action and a version. Use a business reference that lets you find the related scheduled work without embedding payment details in the payload. Keep access to the billing system on your server.
Schedule a check of the invoice
The illustrative request below delivers a small identifier to your endpoint one day later. The endpoint should look up the current invoice before deciding whether a notification is appropriate. It should not blindly replay an email assembled when the original failure occurred.
Illustrative server-side example: this schedules delivery 24 hours from execution. Replace the environment variables and example record IDs, persist the returned job ID, and implement your own authenticated HTTPS receiver. The request follows the current API reference.
const apiKey = process.env.WEBHOOK_SCHEDULER_API_KEY;
const receiverUrl = process.env.REMINDER_WEBHOOK_URL;
if (!apiKey || !receiverUrl) throw new Error("Set the server-side API key and public HTTPS receiver URL.");
const response = await fetch("https://webhookscheduler.com/api/v1/schedule", {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
body: JSON.stringify({
url: receiverUrl,
method: "POST",
runAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
reference: "invoice:example-123",
idempotencyKey: "invoice-example-123-reminder-v1",
body: { resourceId: "example-123" },
}),
});
if (!response.ok) throw new Error(`Scheduling failed: ${response.status}`);
const job = await response.json();
if (!job.id) throw new Error("Missing scheduled job ID");
// Persist job.id with your business record before acknowledging this work.
console.log(job.id);
// Call with the stored ID when the reminder becomes obsolete.
async function cancelReminder(jobId) {
const result = await fetch(
`https://webhookscheduler.com/api/v1/jobs/${encodeURIComponent(jobId)}/cancel`,
{ method: "POST", headers: { Authorization: `Bearer ${apiKey}` } },
);
if (!result.ok) throw new Error(`Cancellation not confirmed: ${result.status}`);
}
Cancellation succeeds only while a job is PENDING or RETRYING. The API documents 400 for a job that is no longer cancelable, 404 for a job not found, and 409 when the state changes before cancellation is applied. These responses do not prove a business action succeeded. Inspect the job and keep the current-state check at the receiver: an HTTP request already in flight cannot be retracted.
Persist the returned id beside the follow-up record. If the schedule succeeds but saving that ID fails, use the same scheduling idempotency key to recover the retained original job. Generating a new key after every timeout could leave several follow-ups for the same event.
Close the loop when the invoice changes
When your application confirms that an invoice is no longer eligible, mark the follow-up obsolete locally and request cancellation. If the cancellation request fails, retain it for retry or reconciliation. A failure to clean up scheduled work should not undo a successful payment.
At delivery time, authenticate the request and read the current invoice state. If another system owns that state and cannot be reached, decide explicitly whether the receiver should retry later or leave the event for investigation. An unavailable lookup should not silently become permission to contact the customer.
Keep a record of the business effect
Claim each follow-up event with a unique business key or transaction before attempting its effect. Where the notification provider supports idempotency, pass a stable key to that provider too. A scheduler's idempotency key only covers creation of scheduled jobs; it does not deduplicate emails or billing actions.
Consider the timeout after a provider may have accepted a message. You need a way to reconcile that uncertain outcome. Do not describe a local flag and an external HTTP call as an exactly-once transaction. Keep the outcome and the time of the last invoice-state check visible to an operator.
Decide what to operate yourself
WebhookScheduler is built by AllClearStack. A managed scheduler can supply the delayed HTTP delivery while your application owns billing decisions and notification policy. It does not replace the billing provider or interpret its events for you.
If a billing platform already offers the exact follow-up sequence you need, extending it may be simpler. A database worker also remains a valid option when your team already operates one. Compare the missing responsibilities with the workflow templates, including cancellation, reconciliation and receiver security, before adding another service.
Disclosure · Built by the AllClearStack team
When ownership is the expensive part
Webhook Scheduler handles delayed HTTP delivery, automatic retries, per-attempt logs, and one-call cancellation. Try a real delivery without creating an account.