Scheduled Payments
Overview
Section titled “Overview”ElasticPay supports two ways to schedule a payment for a future date:
- Payment Plans — the system automatically generates and processes recurring payment intents on your behalf (weekly, monthly, etc.)
- Direct PI scheduling — you create a single payment intent now and set
scheduled_payment_dateto a future date; the system processes it automatically on that date
This guide covers direct PI scheduling. For a full reference of payment plan lifecycle management, see Payment Plans API.
Requirements
Section titled “Requirements”- A saved payment method (
pm_xxx) must be attached to the intent at create time. Transient tokens (from the widget) are not accepted because the customer will not be present at processing time. - The date must be today or in the future, and within 365 days of today, in your time zone (or on the customer’s test clock). A payment plan’s dates follow the same rule.
Step 1 — Save a payment method
Section titled “Step 1 — Save a payment method”If you don’t have a saved pm_xxx yet, create a Setup Intent to collect and save the customer’s card:
curl -X POST https://api.elasticpay.co/api/v1/setup_intents \ -H "Authorization: Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{"customer_id": "cus_0abc123def456"}'const res = await fetch("https://api.elasticpay.co/api/v1/setup_intents", { method: "POST", headers: { "Authorization": "Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "Content-Type": "application/json", }, body: JSON.stringify({ customer_id: "cus_0abc123def456" }),});const { client_secret } = await res.json();import requests
res = requests.post( "https://api.elasticpay.co/api/v1/setup_intents", headers={ "Authorization": "Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "Content-Type": "application/json", }, json={"customer_id": "cus_0abc123def456"},)client_secret = res.json()["client_secret"]<?phpuse GuzzleHttp\Client;
$client = new Client([ 'base_uri' => 'https://api.elasticpay.co', 'headers' => [ 'Authorization' => 'Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'Content-Type' => 'application/json', ],]);
$res = $client->post('/api/v1/setup_intents', [ 'json' => ['customer_id' => 'cus_0abc123def456'],]);$data = json_decode($res->getBody(), true);$client_secret = $data['client_secret'];require 'faraday'require 'json'
conn = Faraday.new('https://api.elasticpay.co') do |f| f.request :json f.response :jsonendconn.headers['Authorization'] = 'Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'conn.headers['Content-Type'] = 'application/json'
res = conn.post('/api/v1/setup_intents') do |req| req.body = { customer_id: 'cus_0abc123def456' }endclient_secret = res.body['client_secret']using System.Net.Http;using System.Net.Http.Headers;using System.Net.Http.Json;
using var http = new HttpClient { BaseAddress = new Uri("https://api.elasticpay.co") };http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
var res = await http.PostAsJsonAsync("/api/v1/setup_intents", new { customer_id = "cus_0abc123def456" });var si = await res.Content.ReadFromJsonAsync<JsonElement>();var clientSecret = si.GetProperty("client_secret").GetString();Pass the client_secret to the payment widget. After the customer completes the form, the saved method pm_xxx is available on the customer record.
Step 2 — Create a scheduled payment intent
Section titled “Step 2 — Create a scheduled payment intent”curl -X POST https://api.elasticpay.co/api/v1/payment_intents \ -H "Authorization: Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "amount": 9900, "currency": "AUD", "customer_id": "cus_0abc123def456", "payment_method": "pm_0xyz789abc123def456ghi012jkl", "scheduled_payment_date": "2026-07-01" }'const res = await fetch("https://api.elasticpay.co/api/v1/payment_intents", { method: "POST", headers: { "Authorization": "Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "Content-Type": "application/json", }, body: JSON.stringify({ amount: 9900, currency: "AUD", customer_id: "cus_0abc123def456", payment_method: "pm_0xyz789abc123def456ghi012jkl", scheduled_payment_date: "2026-07-01", }),});const pi = await res.json();import requests
res = requests.post( "https://api.elasticpay.co/api/v1/payment_intents", headers={ "Authorization": "Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "Content-Type": "application/json", }, json={ "amount": 9900, "currency": "AUD", "customer_id": "cus_0abc123def456", "payment_method": "pm_0xyz789abc123def456ghi012jkl", "scheduled_payment_date": "2026-07-01", },)pi = res.json()<?phpuse GuzzleHttp\Client;
$client = new Client([ 'base_uri' => 'https://api.elasticpay.co', 'headers' => [ 'Authorization' => 'Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'Content-Type' => 'application/json', ],]);
$res = $client->post('/api/v1/payment_intents', [ 'json' => [ 'amount' => 9900, 'currency' => 'AUD', 'customer_id' => 'cus_0abc123def456', 'payment_method' => 'pm_0xyz789abc123def456ghi012jkl', 'scheduled_payment_date' => '2026-07-01', ],]);$pi = json_decode($res->getBody(), true);require 'faraday'require 'json'
conn = Faraday.new('https://api.elasticpay.co') do |f| f.request :json f.response :jsonendconn.headers['Authorization'] = 'Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'conn.headers['Content-Type'] = 'application/json'
res = conn.post('/api/v1/payment_intents') do |req| req.body = { amount: 9900, currency: 'AUD', customer_id: 'cus_0abc123def456', payment_method: 'pm_0xyz789abc123def456ghi012jkl', scheduled_payment_date: '2026-07-01' }endpi = res.bodyusing System.Net.Http;using System.Net.Http.Headers;using System.Net.Http.Json;
using var http = new HttpClient { BaseAddress = new Uri("https://api.elasticpay.co") };http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
var res = await http.PostAsJsonAsync("/api/v1/payment_intents", new { amount = 9900, currency = "AUD", customer_id = "cus_0abc123def456", payment_method = "pm_0xyz789abc123def456ghi012jkl", scheduled_payment_date = "2026-07-01",});var pi = await res.Content.ReadFromJsonAsync<JsonElement>();The response will have status: "requires_confirmation" and scheduled_payment_date: "2026-07-01".
Step 3 — Confirm to lock in the schedule
Section titled “Step 3 — Confirm to lock in the schedule”curl -X POST https://api.elasticpay.co/api/v1/payment_intents/pi_0abc123/confirm \ -H "Authorization: Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{}'const res = await fetch( "https://api.elasticpay.co/api/v1/payment_intents/pi_0abc123/confirm", { method: "POST", headers: { "Authorization": "Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "Content-Type": "application/json", }, body: JSON.stringify({}), });const result = await res.json();import requests
res = requests.post( "https://api.elasticpay.co/api/v1/payment_intents/pi_0abc123/confirm", headers={ "Authorization": "Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "Content-Type": "application/json", }, json={},)result = res.json()<?phpuse GuzzleHttp\Client;
$client = new Client([ 'base_uri' => 'https://api.elasticpay.co', 'headers' => [ 'Authorization' => 'Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'Content-Type' => 'application/json', ],]);
$res = $client->post('/api/v1/payment_intents/pi_0abc123/confirm', ['json' => []]);$result = json_decode($res->getBody(), true);require 'faraday'require 'json'
conn = Faraday.new('https://api.elasticpay.co') do |f| f.request :json f.response :jsonendconn.headers['Authorization'] = 'Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'conn.headers['Content-Type'] = 'application/json'
res = conn.post('/api/v1/payment_intents/pi_0abc123/confirm') do |req| req.body = {}endresult = res.bodyusing System.Net.Http;using System.Net.Http.Headers;using System.Net.Http.Json;
using var http = new HttpClient { BaseAddress = new Uri("https://api.elasticpay.co") };http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
var res = await http.PostAsJsonAsync( "/api/v1/payment_intents/pi_0abc123/confirm", new {});var result = await res.Content.ReadFromJsonAsync<JsonElement>();Because the date is in the future, the response status will be ready — the payment is queued for automatic processing on scheduled_payment_date. No further action is needed.
ready status
Section titled “ready status”A payment intent in ready status has been confirmed and is waiting for its scheduled date.
| Status | Meaning |
|---|---|
ready |
Confirmed, waiting for scheduled date |
processing |
Submitted to PSP |
succeeded |
Payment collected |
failed |
Payment declined or errored |
If a problem on our side stops a scheduled payment being sent on its date, it stays ready and we keep
trying while we fix the cause. Nothing is charged until it’s sent, and you don’t need to do anything: it’s
collected once it goes through.
Webhook events
Section titled “Webhook events”Subscribe to these events to monitor scheduled payment activity:
| Event | When |
|---|---|
payment_intent.succeeded |
Scheduled payment collected successfully |
payment_intent.failed |
Payment declined or failed |
payment_intent.processing |
Submitted to PSP, awaiting result |
See Webhooks for how to handle these events.