# Asynchronous payments (/en/integrate/asynchronous-payments)

Some payment methods are paid after checkout. The session completes first and the money arrives later, so fulfil on paymentStatus, not on completion.

Fawry is available on every account and on every integration, including Hosted Checkout and Payment Links, so every webhook handler must handle this. With Fawry, the customer gets a reference number at checkout and pays it later at an outlet or in the Fawry app. The checkout is over before the money arrives.

One rule covers it: `status` says whether the customer is done with checkout, `paymentStatus` says whether the money has arrived. Fulfil on `paymentStatus`, never on `status` alone.

<Callout type="warn">
  `checkout.session.completed` arrives with `paymentStatus: "unpaid"` for a Fawry payment. A handler
  that fulfils on the event without reading `paymentStatus` ships orders that were never paid.
</Callout>

## The three events [#the-three-events]

| Event                                      | `paymentStatus` | What you do                                          |
| ------------------------------------------ | --------------- | ---------------------------------------------------- |
| `checkout.session.completed`               | `unpaid`        | Record the order as awaiting payment. Do not fulfil. |
| `checkout.session.async_payment_succeeded` | `paid`          | Fulfil the order.                                    |
| `checkout.session.async_payment_failed`    | `unpaid`        | Close the order. The customer needs a new session.   |

Cards and ValU fire `checkout.session.completed` with `paymentStatus: "paid"` and nothing else.

`data.object` on all three is the full <ApiLink href="/api-reference/objects/checkout-session">Checkout Session</ApiLink>, so one handler serves every event. The reference the customer pays is at `paymentIntent.nextAction.displayVoucherDetails` (`reference`, `expiresAt`, `instructions`).

## One fulfilment function [#one-fulfilment-function]

Guard fulfilment on `paymentStatus` and call it from both success events. Make it safe to run twice for the same session: deliveries are retried.

```typescript
async function fulfillCheckout(session: CheckoutSession) {
  if (session.paymentStatus !== "paid") return;
  if (await orders.isFulfilled(session.id)) return;
  await orders.fulfill(session.id, session.lineItems);
}

app.post("/webhooks/xpay", async (req, res) => {
  const event = verifyAndParse(req);
  const session = event.data.object;

  switch (event.type) {
    case "checkout.session.completed":
    case "checkout.session.async_payment_succeeded":
      await fulfillCheckout(session);
      break;
    case "checkout.session.async_payment_failed":
      await orders.markUnpaid(session.id);
      break;
  }

  res.sendStatus(200);
});
```

## What the customer sees [#what-the-customer-sees]

On Hosted Checkout the customer sees the reference, presses **Done**, and lands on the confirmation page in its awaiting-payment state or on your `afterCompletion.redirect.url`. Reopening the session shows the reference until it is paid.

With Drop-in and Elements the reference opens in the same overlay as a 3D Secure challenge. On **Done**, `confirm()` resolves with `{ type: "success", session }` where `session.status.paymentStatus` is `"unpaid"`, and Drop-in's `onComplete` fires with `paymentStatus: "unpaid"`. Show an awaiting-payment page, not a thank-you page.

A completed session cannot be paid another way. A customer who changes their mind starts a new session.

## Where to next [#where-to-next]

<Cards>
  <Card icon="<Webhook />" title="Event reference" href="/integrate/webhooks/event-reference">
    Every event and when it fires.
  </Card>

  <Card icon="<Braces />" title="Checkout Session" href="/integrate/checkout-session/overview">
    The `status` and `paymentStatus` states.
  </Card>

  <Card icon="<FlaskConical />" title="Test mode" href="/get-started/test-mode">
    Pay a test Fawry reference and watch the events arrive.
  </Card>

  <Card icon="<ListTree />" title="After completion" href="/integrate/checkout-session/after-completion">
    What your return page does when the payment is not confirmed yet.
  </Card>
</Cards>