Order fulfillment

Fulfill orders and deliver goods or services after successful payment through Onerway Checkout. Learn webhook integration patterns and automated fulfillment best practices.
Payment results must be determined by webhook notifications. Do not rely solely on the status field in synchronous responses! Synchronous responses may not reflect the final payment state, especially for delayed payment methods like bank transfers or BNPL services.

Order fulfillment after successful payment

When you receive a payment through Onerway Checkout (including Payment Links), you need to provide your customer with what they paid for. This process is known as fulfillment. Common fulfillment scenarios include:

  • Digital delivery: Grant membership access, issue license keys, or provision SaaS tenant accounts
  • Physical delivery: Ship products to customers through your warehouse management system
  • Transaction records: Store transaction details and product information for reconciliation, risk management, and after-sales support

You have two ways to handle fulfillment:

  • Manually: Monitor the , check payment notifications, and manually fulfill orders
  • Automatically: Build an automated fulfillment system Recommended

The manual option works for low transaction volumes or pilot operations, but for most situations we recommend automating fulfillment. The rest of this guide shows you how to build an automatic fulfillment system.


Automatic fulfillment

The automatic fulfillment system uses a dual-trigger mechanism combining webhooks (server-to-server notifications) and return URL redirects (landing page after payment):

  • Webhooks ensure every payment triggers fulfillment. Onerway sends an initial notification and retries up to 2 more times at 30-minute intervals if your endpoint does not return the correct response
  • Landing page triggers provide immediate fulfillment when the customer is present, reducing perceived delays from webhook latency

You must use webhooks to ensure fulfillment happens for every payment. Landing page triggers optimize the experience by providing instant access when customers complete checkout.


Implement idempotent fulfillment function Server-side

Create a function on your server to fulfill successful payments. Webhooks trigger this function, and it's also called when customers land on your return URL after completing checkout. This guide refers to this function as fulfill_checkout, but you can name it whatever you wish.

Perform fulfillment only once per payment. Because of how webhooks and internet connections work, your fulfill_checkout function might be called multiple times, possibly concurrently, for the same transaction. Handling this correctly ensures you don't duplicate fulfillment.

Function requirements

Your fulfill_checkout function must handle the following:

Correctly handle being called multiple times, possibly concurrently, with the same merchantTxnId. Use database transactions or distributed locks to prevent race conditions.

Use merchantTxnId as the input parameter for transaction correlation and idempotency checks.

Query transaction details and payment information through the Transaction Order Query APIPayments API.

Check status in combination with txnType to determine if fulfillment is required. Only fulfill when status = S and txnType = SALE.

Provision benefits, ship products, or trigger your fulfillment workflows based on your business needs.

Persist fulfillment records in your database to prevent duplicate processing and enable auditing.

Implementation example

Use the code below as a starting point for your fulfill_checkout function. The TODO comments indicate functionality you must implement.

async function fulfill_checkout(merchantTxnId) {
  // TODO: Check if already fulfilled (idempotency)
  const existing = await db.getFulfillment(merchantTxnId);
  if (existing) {
    return existing;
  }

  // Retrieve the transaction from the Transaction Order Query API
  const { content } = await onerway.queryTransactions({
    merchantTxnIds: merchantTxnId
  });
  const txn = content?.[0];
  if (!txn) {
    return { success: false, reason: 'Transaction not found' };
  }

  // Verify transaction type and status
  if (txn.txnType !== 'SALE') {
    return { success: false, reason: 'Invalid transaction type' };
  }

  // Only fulfill when payment is successful
  if (txn.status !== 'S') {
    return { success: false, reason: 'Payment not completed' };
  }

  // TODO: Perform fulfillment
  await provisionService(txn);

  // TODO: Record fulfillment in database
  const fulfillment = await db.saveFulfillment({
    merchantTxnId,
    status: 'fulfilled',
    timestamp: new Date()
  });

  return { success: true, merchantTxnId, fulfillment };
}

Common fulfillment actions

Depending on your business needs, your fulfill_checkout function might need to:

  • Provision access to services or digital content
  • Trigger shipment of physical goods through your warehouse system
  • Save transaction details and order information in your database
  • Send custom order confirmation emails to customers
  • Update inventory or stock records
  • Record transactions for accounting and reconciliation
For digital services, consider implementing immediate provisioning on the landing page for instant access, with webhook-based fulfillment as a backup to ensure reliability.

Create webhook payment handler Server-side

Create a webhook event handler to listen for payment events and trigger your fulfill_checkout function.

When someone pays you, Onerway sends a webhook notification. Set up an endpoint on your server to accept, process, and confirm receipt of these events.

Webhook event types

Listen for the following event to trigger fulfillment:

  • notifyType = TXN + txnType = SALE + status = S: Payment completed successfully
  • notifyType = TXN + txnType = SALE + status = F: Payment failed or was declined
    Status values:
  • S = Success (fulfill the order)
  • F = Failed/Declined/Rejected (do not fulfill)
    See Transaction NotificationPayments API for complete event structure.

Implementation example

// Webhook endpoint handler
app.post('/webhook', async (req, res) => {
  const payload = req.body;

  // TODO: Verify the webhook signature
  // See: https://docs.onerway.com/apis/en/v0.6/sign
  if (!verifySignature(payload, process.env.MERCHANT_SECRET_KEY)) {
    return res.status(401).send('Invalid signature');
  }

  // Return only the transactionId value (not a JSON object)
  res.status(200).send(payload.transactionId);

  // Process fulfillment asynchronously to avoid timeout
  if (payload.notifyType === 'TXN' &&
      payload.txnType === 'SALE' &&
      (payload.status === 'S' || payload.status === 'F')) {

    setImmediate(async () => {
      try {
        await fulfill_checkout(payload.merchantTxnId);
      } catch (error) {
        console.error(`Fulfillment error: ${error.message}`);
        // TODO: Implement error handling and alerting
      }
    });
  }
});

Webhook handling best practices

  • Verify signatures: Always validate webhook authenticity. See Signature Verification GuidePayments API for implementation details
  • Respond correctly: Return only the transactionId value (not a JSON object). Any other response triggers retries
  • Respond quickly: Return acknowledgment immediately to avoid retries
  • Process asynchronously: Handle fulfillment in a background job to prevent timeouts
  • Handle idempotency: Use transactionId to deduplicate webhook deliveries and merchantTxnId to prevent duplicate fulfillment

Test your integration Recommended

The quickest way to test your webhook handler is to simulate webhook events locally:

  1. Start your local server (for example, on localhost:3000)
  2. Configure test webhook URL using a tunneling tool:
# Using ngrok to expose local server
ngrok http 3000

# Copy the HTTPS URL (e.g., https://abc123.ngrok.io)
  1. Set your webhook URL in the payment request:
  • Set notifyUrl to your public webhook endpoint (for example https://abc123.ngrok.io/webhook)
  • Save your webhook secret for signature verification

  1. Create a test Checkout session using your sandbox API keys
  2. Complete the payment with test payment credentials
  3. Verify webhook delivery:
  • Check your server logs for the webhook event
  • Verify signature validation passed
  • Confirm fulfill_checkout was called with correct merchantTxnId

After completing a test payment, verify:


Production deployment Required

Configure webhook and return URLs

When creating a Checkout session, include both notifyUrl and returnUrl in the txnOrderMsg parameter:

{
  "merchantNo": "800209",
  "merchantTxnId": "e868e769-afe5-41f7-9882-04835122e0b3",
  "orderAmount": "100.00",
  "orderCurrency": "USD",
  "txnType": "SALE",
  // ... other required parameters
  "txnOrderMsg": "{
    \"notifyUrl\": \"https://your-domain.com/webhook\",
    \"returnUrl\": \"https://your-domain.com/order-complete?merchantTxnId=e868e769-afe5-41f7-9882-04835122e0b3\",
    \"appId\": \"your-app-id\",
    \"products\": \"[...]\",
    // ... other txnOrderMsg fields
  }"
}

Parameter details:

ParameterDescription
notifyUrlYour webhook endpoint URL to receive payment event notifications
returnUrlYour landing page URL where customers are redirected after payment
We recommend appending merchantTxnId to your returnUrl so you can query the final payment status when customers land on the page. For example: https://your-domain.com/order-complete?merchantTxnId=YOUR_TXN_ID.

Confirm payment with the Order Query API Recommended

Use the Transaction Order Query API to confirm final status, reconcile delayed payments, and safely resume fulfillment if your webhook handler was down. Query by merchantTxnId and only fulfill when the status is final and successful.

Configure landing page for immediate fulfillment Recommended

Webhooks are required to ensure fulfillment for every payment, but webhooks can sometimes be delayed. To optimize your payment flow and provide immediate confirmation when your customer is present, you can trigger a status check from your landing page.

Extract transaction ID and confirm status

When your customer completes payment and is redirected to your returnUrl, confirm the payment status and display the correct next step:

// Landing page handler
app.get('/order-complete', async (req, res) => {
  // 1. Extract merchantTxnId from URL
  const merchantTxnId = req.query.merchantTxnId;

  if (!merchantTxnId) {
    return res.status(400).send('Missing transaction ID');
  }

  // 2. Confirm payment status before fulfillment
  const result = await fulfill_checkout(merchantTxnId);

  // 3. Render the landing page based on the final status
  res.render('order-complete', {
    status: result.success ? 'fulfilled' : 'pending',
    merchantTxnId
  });
});

What to display on your landing page

When you render your landing page, you can display:

  • If status is fulfilled: access links, order confirmation, or tracking info
  • If status is pending: a clear message that payment is processing and fulfillment will occur after confirmation
Do not provide services based only on the landing page redirect. Customers may not finish the redirect, or a payment may still be pending. Always require a confirmed success status from webhook or Order Query before fulfillment.

Delayed payment methods

Asynchronous settlement characteristics

Some payment methods have asynchronous settlement or confirmation characteristics. When a checkout session ends, it doesn't necessarily mean funds are finalized or the payment is ultimately successful.

Examples of delayed payment methods:

Payment MethodTypeTypical Delay
TrustlyBank transfer2-5 days
KonbiniConvenience storeUp to 14 days
OXXOConvenience storeUp to 14 days

Handling delayed payments

For delayed payment methods, the transaction can remain pending until payment either succeeds or fails. Onerway sends the same webhook event (notifyType = TXN, txnType = SALE) when payment eventually completes with the final status.

Fulfillment rules for delayed payments:

  • ✅ Wait for status = S before fulfilling
  • ❌ Do not fulfill when payment is pending or status = F (failed)
Always wait for the success status (S) before fulfilling. Fulfilling orders prematurely can lead to financial losses if the payment fails or is disputed.

You might also want to listen for payment failures and notify customers when a delayed payment doesn't succeed.