Subscription utility code

Use example code to enhance your subscription management via the API
View as MarkdownOpen in Claude
Applies to:Developers

This guide contains code snippets to help you manage subscriptions in your apps using Payabli’s server SDKs. To learn more about Payabli’s server SDKs, see the Server SDKs overview page.

The guide includes:

  • A list of SDK methods for subscription management with links to the reference documentation for each method.
  • A set of examples showing how to implement retry logic for declined subscription payments.

SDK subscription methods

This section covers API endpoints for subscription management and their corresponding methods in Payabli’s server SDKs. See the SDK reference documentation in each endpoint for more details on the request and response formats.

Create subscription

The POST /Subscription/add endpoint creates a new subscription for a paypoint. See Create subscription for the full API reference. The following table lists the methods in each SDK that correspond to this endpoint, along with links to the reference documentation for each method:

SDKMethodReference
TypeScriptsubscription.newSubscription()reference.md
Pythonsubscription.new_subscription()reference.md
C#subscription.NewSubscriptionAsync()reference.md
Gosubscription.NewSubscription()reference.md
PHPsubscription->newSubscription()reference.md
Javasubscription.newSubscription()reference.md
Rubysubscription.new_subscription()reference.md
Rustsubscription.new_subscription()reference.md

This snippet shows how to create a subscription using the POST /Subscription/add endpoint. Select a programming language in the dropdown to see the code example for that language.

POST
/api/Subscription/add
1curl -X POST https://api-sandbox.payabli.com/api/Subscription/add \
2 -H "requestToken: <apiKey>" \
3 -H "Content-Type: application/json" \
4 -d '{
5 "customerData": {
6 "customerId": 4440
7 },
8 "entryPoint": "f743aed24a",
9 "paymentDetails": {
10 "totalAmount": 100,
11 "serviceFee": 0
12 },
13 "paymentMethod": {
14 "initiator": "merchant",
15 "storedMethodId": "4000e8c6-3add-4200-8ac2-9b8a4f8b1639-1323",
16 "storedMethodUsageType": "recurring"
17 },
18 "scheduleDetails": {
19 "endDate": "03-20-2025",
20 "frequency": "weekly",
21 "planId": 1,
22 "startDate": "09-20-2024"
23 }
24}'

This snippet shows the response from the POST /Subscription/add endpoint.

Response
1{
2 "responseText": "Success",
3 "responseData": 396,
4 "customerId": 4440,
5 "isSuccess": true
6}

Get subscription

The GET /Subscription/{subId} endpoint retrieves details for a subscription. See Get subscription for the full API reference. The following table lists the methods in each SDK that correspond to this endpoint, along with links to the reference documentation for each method:

SDKMethodReference
TypeScriptsubscription.getSubscription()reference.md
Pythonsubscription.get_subscription()reference.md
C#subscription.GetSubscriptionAsync()reference.md
Gosubscription.GetSubscription()reference.md
PHPsubscription->getSubscription()reference.md
Javasubscription.getSubscription()reference.md
Rubysubscription.get_subscription()reference.md
Rustsubscription.get_subscription()reference.md

This snippet shows how to get a subscription using the GET /Subscription/{subId} endpoint. Select a programming language in the dropdown to see the code example for that language.

GET
/api/Subscription/:subId
1curl https://api-sandbox.payabli.com/api/Subscription/263 \
2 -H "requestToken: <apiKey>"

This snippet shows the response from the GET /Subscription/{subId} endpoint.

Response
1{
2 "EndDate": "2025-10-19T00:00:00Z",
3 "LastRun": "2025-10-19T00:00:00Z",
4 "NextDate": "2025-10-19T00:00:00Z",
5 "StartDate": "2025-10-19T00:00:00Z",
6 "CreatedAt": "2022-07-01T15:00:01Z",
7 "Customer": {
8 "Identifiers": [
9 "\\\"firstname\\\"",
10 "\\\"lastname\\\"",
11 "\\\"email\\\"",
12 "\\\"customId\\\""
13 ],
14 "FirstName": "John",
15 "LastName": "Doe",
16 "CompanyName": "Sunshine LLC",
17 "BillingAddress1": "1111 West 1st Street",
18 "BillingAddress2": "Suite 200",
19 "BillingCity": "Miami",
20 "BillingState": "FL",
21 "BillingZip": "45567",
22 "BillingCountry": "US",
23 "BillingPhone": "5555555555",
24 "BillingEmail": "example@email.com",
25 "CustomerNumber": "3456-7645A",
26 "ShippingAddress1": "123 Walnut St",
27 "ShippingAddress2": "STE 900",
28 "ShippingCity": "Johnson City",
29 "ShippingState": "TN",
30 "ShippingZip": "37619",
31 "ShippingCountry": "US",
32 "customerId": 4440,
33 "customerStatus": 1,
34 "AdditionalData": null
35 },
36 "EntrypageId": 0,
37 "ExternalPaypointID": "Paypoint-100",
38 "FeeAmount": 3,
39 "Frequency": "monthly",
40 "IdSub": 396,
41 "invoiceData": {
42 "AdditionalData": null,
43 "attachments": [
44 {}
45 ],
46 "company": "ACME, INC",
47 "discount": 10,
48 "dutyAmount": 0,
49 "firstName": "Chad",
50 "freightAmount": 10,
51 "frequency": "onetime",
52 "invoiceAmount": 105,
53 "invoiceDate": "2025-07-01",
54 "invoiceDueDate": "2025-07-01",
55 "invoiceEndDate": "2025-07-01",
56 "invoiceNumber": "INV-2345",
57 "invoiceStatus": 1,
58 "invoiceType": 0,
59 "items": [
60 {
61 "itemCost": 5,
62 "itemProductName": "Materials deposit",
63 "itemQty": 1
64 }
65 ],
66 "lastName": "Mercia",
67 "notes": "Example notes.",
68 "paymentTerms": "PIA",
69 "purchaseOrder": "PO-345",
70 "shippingAddress1": "123 Walnut St",
71 "shippingAddress2": "STE 900",
72 "shippingCity": "Johnson City",
73 "shippingCountry": "US",
74 "shippingEmail": "example@email.com",
75 "shippingFromZip": "30040",
76 "shippingPhone": "5555555555",
77 "shippingState": "TN",
78 "shippingZip": "37619",
79 "summaryCommodityCode": "501718",
80 "tax": 2.05,
81 "termsConditions": "Must be paid before work scheduled."
82 },
83 "LastUpdated": "2022-07-01T15:00:01Z",
84 "LeftCycles": 15,
85 "Method": "card",
86 "NetAmount": 3762.87,
87 "ParentOrgName": "PropertyManager Pro",
88 "PaymentData": {
89 "AccountExp": "11/29",
90 "accountId": "accountId",
91 "AccountType": "visa",
92 "AccountZip": "90210",
93 "binData": {
94 "binMatchedLength": "6",
95 "binCardBrand": "Visa",
96 "binCardType": "Credit",
97 "binCardCategory": "PLATINUM",
98 "binCardIssuer": "Bank of Example",
99 "binCardIssuerCountry": "United States",
100 "binCardIssuerCountryCodeA2": "US",
101 "binCardIssuerCountryNumber": "840",
102 "binCardIsRegulated": "false",
103 "binCardUseCategory": "Consumer",
104 "binCardIssuerCountryCodeA3": "USA"
105 },
106 "HolderName": "Chad Mercia",
107 "Initiator": "payor",
108 "MaskedAccount": "4XXXXXXXX1111",
109 "orderDescription": "Depost for materials for 123 Walnut St",
110 "paymentDetails": {
111 "totalAmount": 100,
112 "categories": [
113 {
114 "amount": 1000,
115 "label": "Deposit"
116 }
117 ],
118 "checkImage": {
119 "key": "value"
120 },
121 "checkNumber": "107",
122 "currency": "USD",
123 "serviceFee": 0,
124 "splitFunding": [
125 {}
126 ]
127 },
128 "Sequence": "subsequent",
129 "SignatureData": "SignatureData",
130 "StoredId": "1ec55af9-7b5a-4ff0-81ed-c12d2f95e135-4440",
131 "StoredMethodUsageType": "subscription"
132 },
133 "PaypointDbaname": "Sunshine Gutters",
134 "PaypointEntryname": "d193cf9a46",
135 "PaypointId": 255,
136 "PaypointLegalname": "Sunshine Services, LLC",
137 "PlanId": 0,
138 "Source": "api",
139 "SubEvents": [
140 {
141 "description": "TransferCreated",
142 "eventTime": "2023-07-05T22:31:06Z",
143 "extraData": {
144 "key": "value"
145 },
146 "refData": "refData",
147 "source": "api"
148 }
149 ],
150 "SubStatus": 1,
151 "TotalAmount": 103,
152 "TotalCycles": 24,
153 "UntilCancelled": true
154}

Update subscription

The PUT /Subscription/{subId} endpoint updates an existing subscription. See Update subscription for the full API reference. The following table lists the methods in each SDK that correspond to this endpoint, along with links to the reference documentation for each method:

SDKMethodReference
TypeScriptsubscription.updateSubscription()reference.md
Pythonsubscription.update_subscription()reference.md
C#subscription.UpdateSubscriptionAsync()reference.md
Gosubscription.UpdateSubscription()reference.md
PHPsubscription->updateSubscription()reference.md
Javasubscription.updateSubscription()reference.md
Rubysubscription.update_subscription()reference.md
Rustsubscription.update_subscription()reference.md

This snippet shows how to update a subscription using the PUT /Subscription/{subId} endpoint. Select a programming language in the dropdown to see the code example for that language.

PUT
/api/Subscription/:subId
1curl -X PUT https://api-sandbox.payabli.com/api/Subscription/231 \
2 -H "requestToken: <apiKey>" \
3 -H "Content-Type: application/json" \
4 -d '{
5 "setPause": true
6}'

This snippet shows the response from the PUT /Subscription/{subId} endpoint.

Response
1{
2 "responseText": "Success",
3 "isSuccess": true,
4 "responseData": "396 paused",
5 "customerId": 4440
6}

Delete subscription

The DELETE /Subscription/{subId} endpoint deletes a subscription and prevents future charges. See Delete subscription for the full API reference. The following table lists the methods in each SDK that correspond to this endpoint, along with links to the reference documentation for each method:

SDKMethodReference
TypeScriptsubscription.removeSubscription()reference.md
Pythonsubscription.remove_subscription()reference.md
C#subscription.RemoveSubscriptionAsync()reference.md
Gosubscription.RemoveSubscription()reference.md
PHPsubscription->removeSubscription()reference.md
Javasubscription.removeSubscription()reference.md
Rubysubscription.remove_subscription()reference.md
Rustsubscription.remove_subscription()reference.md

This snippet shows how to delete a subscription using the DELETE /Subscription/{subId} endpoint. Select a programming language in the dropdown to see the code example for that language.

DELETE
/api/Subscription/:subId
1curl -X DELETE https://api-sandbox.payabli.com/api/Subscription/396 \
2 -H "requestToken: <apiKey>"

This snippet shows the response from the DELETE /Subscription/{subId} endpoint.

Response
1{
2 "responseText": "Success",
3 "isSuccess": true,
4 "responseData": "396"
5}

List subscriptions by paypoint

The GET /Query/subscriptions/{entry} endpoint returns all subscriptions for a paypoint. See List subscriptions by paypoint for the full API reference. The following table lists the methods in each SDK that correspond to this endpoint, along with links to the reference documentation for each method:

SDKMethodReference
TypeScriptquery.listSubscriptions()reference.md
Pythonquery.list_subscriptions()reference.md
C#query.ListSubscriptionsAsync()reference.md
Goquery.ListSubscriptions()reference.md
PHPquery->listSubscriptions()reference.md
Javaquery.listSubscriptions()reference.md
Rubyquery.list_subscriptions()reference.md
Rustquery.list_subscriptions()reference.md

This snippet shows how to list subscriptions by paypoint using the GET /Query/subscriptions/{entry} endpoint. Select a programming language in the dropdown to see the code example for that language.

GET
/api/Query/subscriptions/:entry
1curl -G https://api-sandbox.payabli.com/api/Query/subscriptions/8cfec329267 \
2 -H "requestToken: <apiKey>" \
3 -d fromRecord=251 \
4 -d limitRecord=0 \
5 -d sortBy=desc(field_name)

This snippet shows the response from the GET /Query/subscriptions/{entry} endpoint.

Response
1{
2 "Records": [
3 {
4 "EndDate": "2026-03-20T00:00:00Z",
5 "LastRun": "2024-01-02T14:32:11Z",
6 "NextDate": "2024-07-20T00:00:00Z",
7 "StartDate": "2024-07-20T00:00:00Z",
8 "StoredMethod": {
9 "IdPmethod": "6edcbb56-9c0e-4003-b3d1-99abf149ba0e",
10 "Method": "card",
11 "Descriptor": "Visa for subscriptions",
12 "MaskedAccount": "4XXXXXXX0010",
13 "ExpDate": "0924",
14 "HolderName": "Lisandra Smitch",
15 "AchSecCode": null,
16 "AchHolderType": null,
17 "IsValidatedACH": false,
18 "BIN": "",
19 "binData": {
20 "binMatchedLength": "6",
21 "binCardBrand": "Visa",
22 "binCardType": "Credit",
23 "binCardCategory": "PLATINUM",
24 "binCardIssuer": "Bank of Example",
25 "binCardIssuerCountry": "United States",
26 "binCardIssuerCountryCodeA2": "US",
27 "binCardIssuerCountryNumber": "840",
28 "binCardIsRegulated": "false",
29 "binCardUseCategory": "Consumer",
30 "binCardIssuerCountryCodeA3": "USA"
31 },
32 "ABA": "",
33 "PostalCode": "37612",
34 "MethodType": "Single Merchant",
35 "LastUpdated": "2023-12-14T08:51:10Z",
36 "CardUpdatedOn": null
37 },
38 "CreatedAt": "2023-12-14T08:51:10Z",
39 "Customer": {
40 "Identifiers": [
41 "\\\"firstname\\\"",
42 "\\\"lastname\\\"",
43 "\\\"email\\\""
44 ],
45 "FirstName": "Lisandra",
46 "LastName": "Smitch",
47 "CompanyName": "Sunshine LLC",
48 "BillingAddress1": "68 Golden Drive",
49 "BillingAddress2": "",
50 "BillingCity": "Johnson City",
51 "BillingState": "TN",
52 "BillingZip": "37612",
53 "BillingCountry": "US",
54 "BillingPhone": "",
55 "BillingEmail": "company@payabli.com",
56 "CustomerNumber": "1234",
57 "ShippingAddress1": "68 Golden Drive",
58 "ShippingCity": "Johnson City",
59 "ShippingState": "TN",
60 "ShippingZip": "37612",
61 "ShippingCountry": "US",
62 "customerId": 1323,
63 "customerStatus": 1,
64 "AdditionalData": null
65 },
66 "EntrypageId": 0,
67 "ExternalPaypointID": "f743aed24a-10",
68 "FeeAmount": 0,
69 "Frequency": "monthly",
70 "IdSub": 580,
71 "invoiceData": {
72 "AdditionalData": null,
73 "frequency": "onetime",
74 "invoiceAmount": 100,
75 "invoiceNumber": "QA-1702561870",
76 "invoiceStatus": 1,
77 "invoiceType": 1,
78 "items": [
79 {
80 "itemCost": 10,
81 "itemDescription": "service",
82 "itemMode": 1,
83 "itemProductName": "Mat replacement",
84 "itemQty": 5,
85 "itemTotalAmount": 50
86 },
87 {
88 "itemCost": 5,
89 "itemDescription": "service",
90 "itemMode": 1,
91 "itemProductName": "Mat clean",
92 "itemQty": 10,
93 "itemTotalAmount": 50
94 }
95 ]
96 },
97 "LastUpdated": "2023-12-14T08:51:10Z",
98 "LeftCycles": 20,
99 "Method": "card",
100 "NetAmount": 10,
101 "ParentOrgName": "FitnessManager",
102 "PaymentData": {
103 "AccountExp": "0924",
104 "AccountType": "unknown",
105 "AccountZip": "37612",
106 "binData": {
107 "binMatchedLength": "6",
108 "binCardBrand": "Visa",
109 "binCardType": "Credit",
110 "binCardCategory": "PLATINUM",
111 "binCardIssuer": "Bank of Example",
112 "binCardIssuerCountry": "United States",
113 "binCardIssuerCountryCodeA2": "US",
114 "binCardIssuerCountryNumber": "840",
115 "binCardIsRegulated": "false",
116 "binCardUseCategory": "Consumer",
117 "binCardIssuerCountryCodeA3": "USA"
118 },
119 "HolderName": "Lisandra Smitch",
120 "Initiator": "payor",
121 "MaskedAccount": "2222 4XXXXXX0010",
122 "paymentDetails": {
123 "totalAmount": 100,
124 "currency": "USD",
125 "serviceFee": 0
126 },
127 "Sequence": "subsequent",
128 "StoredMethodUsageType": "subscription"
129 },
130 "PaypointDbaname": "Athlete Factory LLC",
131 "PaypointEntryname": "473ac58b0",
132 "PaypointId": 10,
133 "PaypointLegalname": "Athlete Factory LLC",
134 "PlanId": 1,
135 "SubEvents": [
136 {
137 "description": "created",
138 "eventTime": "2023-12-14T13:51:10Z",
139 "refData": "00-3470dfe2658b492811630255602f3fb5-d06fe0f72110000-00"
140 },
141 {
142 "description": "updated",
143 "eventTime": "2023-12-15T10:30:00Z",
144 "refData": "01-1234abcde6789fghij4567klmnopqr89-abcdefghi12345678-01",
145 "source": "web app"
146 }
147 ],
148 "SubStatus": 1,
149 "TotalAmount": 100,
150 "TotalCycles": 20,
151 "UntilCancelled": false
152 }
153 ],
154 "Summary": {
155 "pageIdentifier": "XXXXXXXXXXXXXXXXXXX",
156 "pageSize": 20,
157 "totalAmount": 150.22,
158 "totalNetAmount": 150.22,
159 "totalPages": 1,
160 "totalRecords": 2
161 }
162}

List subscriptions by organization

The GET /Query/subscriptions/org/{orgId} endpoint returns all subscriptions across for an organization. See List subscriptions by organization for the full API reference. The following table lists the methods in each SDK that correspond to this endpoint, along with links to the reference documentation for each method:

SDKMethodReference
TypeScriptquery.listSubscriptionsOrg()reference.md
Pythonquery.list_subscriptions_org()reference.md
C#query.ListSubscriptionsOrgAsync()reference.md
Goquery.ListSubscriptionsOrg()reference.md
PHPquery->listSubscriptionsOrg()reference.md
Javaquery.listSubscriptionsOrg()reference.md
Rubyquery.list_subscriptions_org()reference.md
Rustquery.list_subscriptions_org()reference.md

This snippet shows how to list subscriptions by organization using the GET /Query/subscriptions/org/{orgId} endpoint. Select a programming language in the dropdown to see the code example for that language.

GET
/api/Query/subscriptions/org/:orgId
1curl -G https://api-sandbox.payabli.com/api/Query/subscriptions/org/123 \
2 -H "requestToken: <apiKey>" \
3 -d fromRecord=251 \
4 -d limitRecord=0 \
5 -d sortBy=desc(field_name)

This snippet shows the response from the GET /Query/subscriptions/org/{orgId} endpoint.

Response
1{
2 "Records": [
3 {
4 "EndDate": "2026-03-20T00:00:00Z",
5 "LastRun": "2024-01-02T14:32:11Z",
6 "NextDate": "2024-07-20T00:00:00Z",
7 "StartDate": "2024-07-20T00:00:00Z",
8 "StoredMethod": {
9 "IdPmethod": "6edcbb56-9c0e-4003-b3d1-99abf149ba0e",
10 "Method": "card",
11 "Descriptor": "Visa for subscriptions",
12 "MaskedAccount": "4XXXXXXX0010",
13 "ExpDate": "0924",
14 "HolderName": "Lisandra Smitch",
15 "AchSecCode": null,
16 "AchHolderType": null,
17 "IsValidatedACH": false,
18 "BIN": "",
19 "binData": {
20 "binMatchedLength": "6",
21 "binCardBrand": "Visa",
22 "binCardType": "Credit",
23 "binCardCategory": "PLATINUM",
24 "binCardIssuer": "Bank of Example",
25 "binCardIssuerCountry": "United States",
26 "binCardIssuerCountryCodeA2": "US",
27 "binCardIssuerCountryNumber": "840",
28 "binCardIsRegulated": "false",
29 "binCardUseCategory": "Consumer",
30 "binCardIssuerCountryCodeA3": "USA"
31 },
32 "ABA": "",
33 "PostalCode": "37612",
34 "MethodType": "Single Merchant",
35 "LastUpdated": "2023-12-14T08:51:10Z",
36 "CardUpdatedOn": null
37 },
38 "CreatedAt": "2023-12-14T08:51:10Z",
39 "Customer": {
40 "Identifiers": [
41 "\\\"firstname\\\"",
42 "\\\"lastname\\\"",
43 "\\\"email\\\""
44 ],
45 "FirstName": "Lisandra",
46 "LastName": "Smitch",
47 "CompanyName": "Sunshine LLC",
48 "BillingAddress1": "68 Golden Drive",
49 "BillingAddress2": "",
50 "BillingCity": "Johnson City",
51 "BillingState": "TN",
52 "BillingZip": "37612",
53 "BillingCountry": "US",
54 "BillingPhone": "",
55 "BillingEmail": "company@payabli.com",
56 "CustomerNumber": "1234",
57 "ShippingAddress1": "68 Golden Drive",
58 "ShippingCity": "Johnson City",
59 "ShippingState": "TN",
60 "ShippingZip": "37612",
61 "ShippingCountry": "US",
62 "customerId": 1323,
63 "customerStatus": 1,
64 "AdditionalData": null
65 },
66 "EntrypageId": 0,
67 "ExternalPaypointID": "f743aed24a-10",
68 "FeeAmount": 0,
69 "Frequency": "monthly",
70 "IdSub": 580,
71 "invoiceData": {
72 "AdditionalData": null,
73 "frequency": "onetime",
74 "invoiceAmount": 100,
75 "invoiceNumber": "QA-1702561870",
76 "invoiceStatus": 1,
77 "invoiceType": 1,
78 "items": [
79 {
80 "itemCost": 10,
81 "itemDescription": "service",
82 "itemMode": 1,
83 "itemProductName": "Mat replacement",
84 "itemQty": 5,
85 "itemTotalAmount": 50
86 },
87 {
88 "itemCost": 5,
89 "itemDescription": "service",
90 "itemMode": 1,
91 "itemProductName": "Mat clean",
92 "itemQty": 10,
93 "itemTotalAmount": 50
94 }
95 ]
96 },
97 "LastUpdated": "2023-12-14T08:51:10Z",
98 "LeftCycles": 20,
99 "Method": "card",
100 "NetAmount": 10,
101 "ParentOrgName": "FitnessManager",
102 "PaymentData": {
103 "AccountExp": "0924",
104 "AccountType": "unknow",
105 "AccountZip": "37612",
106 "binData": {
107 "binMatchedLength": "6",
108 "binCardBrand": "Visa",
109 "binCardType": "Credit",
110 "binCardCategory": "PLATINUM",
111 "binCardIssuer": "Bank of Example",
112 "binCardIssuerCountry": "United States",
113 "binCardIssuerCountryCodeA2": "US",
114 "binCardIssuerCountryNumber": "840",
115 "binCardIsRegulated": "false",
116 "binCardUseCategory": "Consumer",
117 "binCardIssuerCountryCodeA3": "USA"
118 },
119 "HolderName": "Lisandra Smitch",
120 "Initiator": "payor",
121 "MaskedAccount": "2222 4XXXXXX0010",
122 "paymentDetails": {
123 "totalAmount": 100,
124 "currency": "USD",
125 "serviceFee": 0
126 },
127 "Sequence": "subsequent",
128 "StoredMethodUsageType": "subscription"
129 },
130 "PaypointDbaname": "Athlete Factory LLC",
131 "PaypointEntryname": "473ac58b0",
132 "PaypointId": 10,
133 "PaypointLegalname": "Athlete Factory LLC",
134 "PlanId": 1,
135 "SubEvents": [
136 {
137 "description": "created",
138 "eventTime": "2023-12-14T13:51:10Z",
139 "refData": "00-3470dfe2658b492811630255602f3fb5-d06fe0f72110000-00"
140 },
141 {
142 "description": "updated",
143 "eventTime": "2023-12-15T10:30:00Z",
144 "refData": "01-1234abcde6789fghij4567klmnopqr89-abcdefghi12345678-01",
145 "source": "web app"
146 }
147 ],
148 "SubStatus": 1,
149 "TotalAmount": 100,
150 "TotalCycles": 20,
151 "UntilCancelled": false
152 }
153 ],
154 "Summary": {
155 "pageIdentifier": "XXXXXXXXXXXXXXXXXXX",
156 "pageSize": 20,
157 "totalAmount": 150.22,
158 "totalNetAmount": 150.22,
159 "totalPages": 1,
160 "totalRecords": 2
161 }
162}

Subscription retry logic

Sometimes a subscription payment may fail for various reasons, such as insufficient funds, an expired card, or other issues. When a subscription payment declines, you may want to retry the payment or take other actions to ensure the subscription remains active, such as contacting the customer. Payabli doesn’t retry failed subscription payments automatically, but you can follow this guide to implement your own retry logic for declined subscription payments.

Retry flow

Before you can receive webhook notifications for declined payments, you need to create a notification for the DeclinedPayment event. After creating the notification, you can listen for the event in your server and implement the retry logic. Build retry logic based on this flow:

Subscription retry flow from webhook to subscription update

Diagram: Subscription Retry Flow Process

This sequence diagram shows how to handle declined subscription payments:

  1. Server receives webhook payload
  2. Webhook handler checks if Event is DeclinedPayment
    • If not DeclinedPayment: Stop processing
    • If DeclinedPayment: Continue to next step
  3. Webhook handler queries transaction using transId from webhook
  4. Transaction API returns transaction details
  5. Webhook handler checks ScheduleReference field in transaction
    • If ScheduleReference is 0 or doesn’t exist: Not a subscription payment, stop processing
    • If ScheduleReference exists: Continue with subscription ID
  6. Webhook handler requests subscription details from Subscription API
  7. Subscription API returns subscription details
  8. Webhook handler updates subscription or retries payment
  9. Subscription API confirms operation completed

This flow enables custom retry logic for declined subscription payments. Payabli doesn’t automatically retry failed subscription payments.

1

Receive Webhook

Set up an endpoint in your server to receive webhooks.

2

Listen for DeclinedPayment

For every webhook received, check if the Event field has a value of DeclinedPayment.

3

Fetch Transaction

If the Event field has a value of DeclinedPayment, query the transaction details using the transId field from the webhook payload.

4

Check for Subscription

From the transaction details, fetch the subscription ID which is stored in the ScheduleReference field. If this value is 0 or not found, this declined payment isn’t associated with a subscription.

5

Fetch Subscription

Use the subscription ID to fetch the subscription details.

6

Operate on Subscription

Use the subscription ID to perform business logic. Some examples include: updating the subscription with a new payment method, retrying the payment, or notifying the customer.

This section covers two examples for implementing retry logic for declined subscription payments:

  • Express.js: A single-file program using Express.js.
  • Next.js: A Next.js API route.

Both examples respond to the DeclinedPayment event for declined subscription payments and update the subscription to use a different payment method.

Examples

The following examples show how to implement retry logic for declined subscription payments.

Before implementing the retry logic, you need to create a webhook notification for the DeclinedPayment event. After the notification is created, you can listen for the event in a server and implement the retry logic. For more information, see Manage Notifications.

1const url = "https://api-sandbox.payabli.com/api/Notification";
2const headers = {
3 "requestToken": "o.Se...RnU=", // Replace with your API key
4 "Content-Type": "application/json"
5};
6
7// Base payload structure
8const basePayload = {
9 content: {
10 timeZone: "-5",
11 webHeaderParameters: [
12 // Replace with your own authentication parameters
13 { key: "myAuthorizationID", value: "1234" }
14 ],
15 eventType: "DeclinedPayment",
16 },
17 method: "web",
18 frequency: "untilcancelled",
19 target: "https://my-app-url.com/", // Replace with your own URL
20 status: 1,
21 ownerType: 2,
22 ownerId: "255" // Replace with your own paypoint ID
23};
24
25// Function to send webhooks
26const sendWebhook = async () => {
27 const payload = basePayload;
28
29 try {
30 const response = await fetch(url, {
31 method: "POST",
32 headers,
33 body: JSON.stringify(payload)
34 });
35
36 const responseText = await response.text();
37 console.log(`Notification for DeclinedPayment, Status: ${response.status}, Response: ${responseText}`);
38 } catch (error) {
39 console.error(`Failed to create notification for DeclinedPayment:`, error);
40 }
41};
42
43sendWebhook();

The Express.js example can be used as a standalone server in a server-side JavaScript or TypeScript runtime such as Node, Bun, or Deno.

TS
1// npm install express
2// npm install --save-dev @types/express
3import express, { Request, Response } from "express";
4
5// Constants for API request
6const ENVIRONMENT: "sandbox" | "production" = "sandbox"; // Change as needed
7const ENTRY = "your-entry"; // Replace with actual entrypoint value
8const API_KEY = "your-api-key"; // Replace with actual API key
9
10// API base URLs based on environment
11const API_BASE_URLS = {
12 sandbox: "https://api-sandbox.payabli.com",
13 production: "https://api.payabli.com",
14};
15
16// Define the expected webhook payload structure
17interface WebhookPayload {
18 Event?: string;
19 transId?: string;
20 [key: string]: any; // Allow additional properties
21}
22
23// Function to handle declined payments
24const handleDeclinedPayment = async (transId?: string): Promise<void> => {
25 if (!transId) {
26 console.log("DeclinedPayment received, but it didn't include a transaction ID.");
27 return Promise.resolve();
28 }
29
30 // Fetch transaction from transId in DeclinedPayment event
31 const transactionQueryUrl = `${API_BASE_URLS[ENVIRONMENT]}/api/Query/transactions/${ENTRY}?transId(eq)=${transId}`;
32 const headers = { requestToken: API_KEY };
33
34 // Get subscription ID from transaction
35 return fetch(transactionQueryUrl, { method: "GET", headers })
36 .then(res => res.ok ? res.json() : Promise.reject(`HTTP ${res.status}: ${res.statusText}`))
37 .then(data => {
38 const subscriptionId = data?.Records[0]?.ScheduleReference;
39 if (!subscriptionId) {
40 console.log("DeclinedPayment notification received, but no subscription ID found.");
41 return;
42 }
43 return subscriptionRetry(subscriptionId); // Perform logic on subscription with subscription ID
44 })
45 .catch(error => console.error(`Error handling declined payment: ${error}`));
46};
47
48const subscriptionRetry = async (subId: string): Promise<void> => {
49 const subscriptionUrl = `${API_BASE_URLS[ENVIRONMENT]}/api/Subscription/${subId}`;
50 const headers = {
51 "Content-Type": "application/json",
52 requestToken: API_KEY,
53 };
54
55 const body = JSON.stringify({
56 setPause: false, // unpause subscription after decline
57 paymentDetails: {
58 storedMethodId: "4000e8c6-...-1323", // Replace with actual stored method ID
59 storedMethodUsageType: "recurring",
60 },
61 scheduleDetails: {
62 startDate: "2025-05-20", // Must be a future date
63 },
64 });
65
66 return fetch(subscriptionUrl, { method: "PUT", headers, body })
67 .then(response =>
68 !response.ok
69 ? Promise.reject(`HTTP ${response.status}: ${response.statusText}`)
70 : response.json())
71 .then(data => console.log("Subscription updated successfully:", data))
72 .catch(error => console.error("Error updating subscription:", error));
73};
74
75const app = express();
76const PORT = 3333;
77
78// Middleware to parse JSON payloads
79app.use(express.json());
80
81// Webhook endpoint
82app.post("/webhook", (req: Request, res: Response): void => {
83 const payload: WebhookPayload = req.body;
84
85 if (payload.Event === "DeclinedPayment") {
86 handleDeclinedPayment(payload.transId);
87 }
88
89 res.sendStatus(200); // Acknowledge receipt
90});
91
92// Start server
93app.listen(PORT, () => {
94 console.log(`Server is running on port ${PORT}, Environment: ${ENVIRONMENT}`);
95});

The Next.js example can’t be used as a standalone server but can be dropped into a Next.js project. See the Next.js API Routes documentation for more information.

TS
1// use in a Next.js project
2// something like /pages/api/webhook-payabli.ts
3import { NextApiRequest, NextApiResponse } from "next";
4
5// Constants for API request
6const ENVIRONMENT: "sandbox" | "production" = "sandbox"; // Change as needed
7const ENTRY = "your-entry"; // Replace with actual entrypoint value
8const API_KEY = "your-api-key"; // Replace with actual API key
9
10// API base URLs based on environment
11const API_BASE_URLS = {
12 sandbox: "https://api-sandbox.payabli.com",
13 production: "https://api.payabli.com",
14};
15
16// Define the expected webhook payload structure
17interface WebhookPayload {
18 Event?: string;
19 transId?: string;
20 [key: string]: any; // Allow additional properties
21}
22
23// Function to handle declined payments
24const handleDeclinedPayment = async (transId?: string): Promise<void> => {
25 if (!transId) {
26 console.log("DeclinedPayment notification received, but it didn't include a transaction ID.");
27 return Promise.resolve();
28 }
29
30 // Fetch transaction from transId in DeclinedPayment event
31 const transactionQueryUrl = `${API_BASE_URLS[ENVIRONMENT]}/api/Query/transactions/${ENTRY}?transId(eq)=${transId}`;
32 const headers = { requestToken: API_KEY };
33
34 // Get subscription ID from transaction
35 return fetch(transactionQueryUrl, { method: "GET", headers })
36 .then(res => res.ok ? res.json() : Promise.reject(`HTTP ${res.status}: ${res.statusText}`))
37 .then(data => {
38 const subscriptionId = data?.Records[0]?.ScheduleReference;
39 if (!subscriptionId) {
40 console.log("DeclinedPayment notification received, but no subscription ID found.");
41 return;
42 }
43 return subscriptionRetry(subscriptionId); // Perform logic on subscription with subscription ID
44 })
45 .catch(error => console.error(`Error handling declined payment: ${error}`));
46};
47
48const subscriptionRetry = async (subId: string): Promise<void> => {
49 const subscriptionUrl = `${API_BASE_URLS[ENVIRONMENT]}/api/Subscription/${subId}`;
50 const headers = {
51 "Content-Type": "application/json",
52 requestToken: API_KEY,
53 };
54
55 const body = JSON.stringify({
56 setPause: false, // unpause subscription after decline
57 paymentDetails: {
58 storedMethodId: "4000e8c6-...-1323", // Replace with actual stored method ID
59 storedMethodUsageType: "recurring",
60 },
61 scheduleDetails: {
62 startDate: "2025-05-20", // Must be a future date
63 },
64 });
65
66 return fetch(subscriptionUrl, { method: "PUT", headers, body })
67 .then(response =>
68 !response.ok
69 ? Promise.reject(`HTTP ${response.status}: ${response.statusText}`)
70 : response.json())
71 .then(data => console.log("Subscription updated successfully:", data))
72 .catch(error => console.error("Error updating subscription:", error));
73};
74
75export default (req: NextApiRequest, res: NextApiResponse): void => {
76 if (req.method === "POST") {
77 const payload: WebhookPayload = req.body;
78
79 if (payload.Event === "DeclinedPayment") {
80 handleDeclinedPayment(payload.transId);
81 }
82
83 res.status(200).end(); // Acknowledge receipt
84 } else {
85 res.setHeader("Allow", ["POST"]);
86 res.status(405).end(`Method ${req.method} Not Allowed`);
87 }
88};

See these related resources to help you get the most out of Payabli.