Postbacks
Get notified when an application changes status, and process those notifications safely.
A postback is an HTTP request PayTomorrow sends to your server when an application reaches a status you need to act on. It's how you learn that a customer was approved, that funding happened, or that an application was cancelled.
Set the destination with notifyUrl when you create an order.
What you receive
A POST with Content-Type: application/x-www-form-urlencoded and User-Agent: PayTomorrow-PostBack/1.0.
POST /webhooks/paytomorrow HTTP/1.1
Host: yourstore.com
Content-Type: application/x-www-form-urlencoded
User-Agent: PayTomorrow-PostBack/1.0
txn_type=cart&payment_status=pending&uuid=3f9c2a7e5b8d4e1fa0c6b2d9e7f41a53&pt_currency=USD&pt_ammount=1149.97&pt_id=482913&pt_app_status=S&pt_tax=99.99&platform=NA&lender=paytomorrow&tax_exempt=no&order_id=100045Prop
Type
When postbacks are sent
payment_status | Status codes | What it means | What to do |
|---|---|---|---|
pending | S W M | The customer finished and signed. For S, you're clear to ship. W and M are waiting on review. | On S, confirm the order and ship. Then settle. On W or M, hold the order. |
completed | C L | The application was funded. | Nothing. Mark the order complete if you track funding. |
cancelled | O N | Cancelled or abandoned. | Release the order. |
failed | U | Declined. | Ask for another payment method. |
refund | T | Refunded. | Cancel or return the order. |
The payload uses pt_app_status to distinguish these cases because pending covers several statuses. Apply the actions above only after reading the authenticated current status and matching your saved order details, as described below.
Treat a postback as a notification
Postbacks are not signed. The payload tells you which application to check; it is not proof of the sender or permission to ship.
- Look up the application token in your own orders. Never create an order from an unknown notification token.
- Save a reconciliation job durably, then return
200. If saving fails, return an error so delivery can retry. - In a worker, serialize reconciliation for that order and call Get application status with your server-side access token.
- Use that authenticated response as the current status. Do not use the incoming
pt_app_statusto authorize fulfillment, cancellation, funding, or refunds. - Check the application against your saved order details with Validate postback and
verifyParams: truebefore releasing goods. A mismatch needs investigation. - Save the reconciled status and any fulfillment job atomically. Give each business action a unique key so concurrent deliveries cannot ship or refund the same order twice.
What VERIFIED_PT actually means
VERIFIED_PT means the application is in one of the validator's accepted statuses and, when requested, matches the supplied order details. Those statuses include review, cancellation, decline, and refund. It does not authenticate the postback sender or confirm that its claimed status is correct. Only an authenticated current status of S, together with matching order details and your fulfillment checks, is a signal to prepare shipment.
Read the authoritative status
These snippets perform the status lookup only. Supply an access token and the application token stored on your order. Add the durable queue, order locking, and transaction boundaries using your application's existing infrastructure.
export async function readPayTomorrowStatus(baseUrl, accessToken, applicationToken) {
const response = await fetch(
`${baseUrl}/ecommerce/application/${encodeURIComponent(applicationToken)}/status`,
{
headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' },
signal: AbortSignal.timeout(15000),
},
);
if (!response.ok) throw new Error(`Status lookup failed: ${response.status}`);
return response.json();
}Match the saved order details
Use your saved customer and cart data, not data from the incoming postback:
curl -X POST https://api-staging.paytomorrow.com/api/application-validation/validateipn \
-H "Content-Type: application/json" \
-d '{
"cmd": "_validate-me",
"uuid": "REPLACE_WITH_YOUR_STORED_APPLICATION_TOKEN",
"verifyParams": true,
"loanAmount": 1149.97,
"firstName": "Jordan",
"lastName": "Rivera650",
"street": "123 Main St",
"city": "Charlotte",
"state": "NC",
"zip": "28202"
}'This example data must be replaced with the details saved on your order. The full response meanings are in the API reference.
Duplicates, delays, and recovery
A queue job should contain the order identity and ask for its current state; it should not replay a status from an old notification. Coalesce duplicate jobs where your queue supports it, lock each order while reconciling, and make side effects idempotent. For example, two workers must not both enqueue a first shipment.
A delayed S notification after funding should cause a fresh status lookup, not change the order back to awaiting shipment. A refund after funding is a legitimate transition; application states are not a single numeric progression. Keep old tokens associated with the order after a partial refund so delayed notifications can be recognized, while reconciling the active replacement application.
If an API lookup or database update fails, retain the job for retry. If order details do not match or the status is unknown, hold fulfillment and investigate. Periodically reconcile unresolved orders so recovery does not depend on notification delivery.
Delivery and retries
- PayTomorrow queues postbacks and sends them within about a minute of the status change.
- Respond with any
2xxstatus to acknowledge. Any other status, a timeout, or a connection error counts as a failure. - Failed postbacks are retried about once a minute, up to 5 attempts in total. After that, use Get application status to catch up.
- Postbacks can arrive more than once and, in rare cases, out of order. Make your handler idempotent and never move an order backwards, for example from funded back to pending.
Don't do slow work before responding
Persist a reconciliation job before returning 200. Do slower API and order work in the worker. Returning success before durable receipt can permanently lose the update.
Pre-approval postback
When a pre-approval is approved, PayTomorrow sends a JSON POST to the pre-approval postback URL configured on your merchant account. Your account manager can set it for you.
{
"firstName": "Jordan",
"lastName": "Rivera",
"street": "123 Main St",
"city": "Charlotte",
"state": "NC",
"zip": "28202",
"email": "jordan.rivera@example.com",
"phone": "7045550123",
"lender": "paytomorrow",
"approvedAmount": 2500
}Try it
Build a realistic postback and send it to your own endpoint from a terminal. It's the fastest way to test your handler before staging calls it.
payment_status=pending. Ship the order, then call Settle application.
curl -X POST 'http://localhost:3000/webhooks/paytomorrow' \ -H 'Content-Type: application/x-www-form-urlencoded' \ -H 'User-Agent: PayTomorrow-PostBack/1.0' \ --data 'txn_type=cart&payment_status=pending&uuid=REPLACE_WITH_STAGING_TOKEN&pt_currency=USD&pt_ammount=1149.97&pt_id=482913&pt_app_status=S&pt_tax=99.99&platform=NA&lender=paytomorrow&tax_exempt=no&order_id=100045'The builder starts with a placeholder application token. Replace it with a real staging token to test reconciliation, or use mocked API responses in an isolated test harness to exercise each scenario. Choosing a status in the builder does not change the real application's status. Keep production verification enabled.