> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pay.aptahq.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Make your first signed request and take your first payment.

This walks you from credentials to a settled charge. Budget about fifteen
minutes.

## Before you start

You need a credential set from AptaPay: `app_id`, `key_id`, `api_key` and
`signing_secret`. You will be issued a **test** set and a **live** set — start
with test.

<Warning>
  Test mode is not a sandbox. Eversend has no sandbox, so test mode is a real
  provider account with a low-balance wallet and a low amount ceiling. Small
  real amounts move. See [Modes](/concepts/modes).
</Warning>

| Environment | Base URL                     |
| ----------- | ---------------------------- |
| Production  | `https://api.pay.aptahq.com` |

<Steps>
  <Step title="Store your credentials">
    Put them in your secret manager, not in source control.

    ```bash .env theme={null}
    APTAPAY_APP_ID=your_app_id
    APTAPAY_KEY_ID=your_key_id
    APTAPAY_API_KEY=ak_test_...
    APTAPAY_SIGNING_SECRET=...
    APTAPAY_OUTBOUND_SECRET=...
    ```

    The signing secret and the outbound secret are **different values**. The
    first signs your requests to us; the second verifies our webhooks to you.
  </Step>

  <Step title="Add the signing helper">
    Every request carries four headers, with an HMAC-SHA256 over a ten-field
    canonical string. Copy the reference implementation from
    [Authentication](/authentication#reference-implementation) — it needs
    nothing but Node's built-in `crypto`.

    There is no login call and no token endpoint. You sign per request.
  </Step>

  <Step title="Confirm auth works">
    Call a cheap read first, so a failure here is unambiguously about signing
    and not about money.

    ```typescript check-auth.ts theme={null}
    const res = await fetch("https://api.pay.aptahq.com/v1/capabilities", {
      headers: signRequest("GET", "/v1/capabilities", new URLSearchParams(), Buffer.alloc(0)),
    });

    console.log(res.status, await res.json());
    ```

    A `200` means your signing is correct. A `401` means it is not — work
    through [401 triage](/authentication#401-triage), starting with your clock.
  </Step>

  <Step title="Check the corridor you need">
    A corridor is country + currency + method. Confirm yours is supported and
    learn its limits rather than hardcoding them.

    ```bash theme={null}
    GET /v1/reference/corridors
    ```

    See [Capabilities and corridors](/concepts/capabilities).
  </Step>

  <Step title="Charge a customer">
    Amounts are **integer minor units**. UGX, RWF, XAF and XOF are
    zero-decimal, so `500000` UGX is five hundred thousand shillings.

    ```typescript charge.ts theme={null}
    import { randomUUID } from "node:crypto";

    const body = Buffer.from(JSON.stringify({
      amount_minor: 5000,
      currency: "UGX",
      country: "UG",
      method: "momo",
      payer: { msisdn: "+256700000000" },
      metadata: { order_id: "ord_123" },
    }));

    const res = await fetch("https://api.pay.aptahq.com/v1/collections", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Idempotency-Key": randomUUID().replace(/-/g, ""),
        ...signRequest("POST", "/v1/collections", new URLSearchParams(), body),
      },
      body,
    });

    const charge = await res.json();
    console.log(charge.reference, charge.status); // APT-... pending
    ```

    You get `201` with a gateway `reference` and, almost always, status
    `pending`. The customer sees their provider's own prompt — a mobile money
    PIN request, a redirect, or an OTP.
  </Step>

  <Step title="Wait for the real answer">
    **A charge does not settle synchronously.** Treat the webhook as the
    outcome, or poll if you must:

    ```bash theme={null}
    GET /v1/collections/{reference}
    ```

    Do not treat `pending` as failure and do not retry it with a new
    idempotency key.
  </Step>

  <Step title="Receive and verify the webhook">
    Register your `callback_url_test`, then verify every delivery before
    trusting it — see [Webhooks](/concepts/webhooks) for the verifier.

    Correlate on `data.metadata`, which echoes what you sent at charge time.
    Do not parse our `reference`; it carries none of your structure.

    ```typescript theme={null}
    const orderId = event.data.metadata?.order_id;
    ```
  </Step>
</Steps>

## What to get right before going live

<AccordionGroup>
  <Accordion title="Branch on the error flags, not the status code" icon="triangle-alert">
    Every error carries exactly one of `terminal`, `retriable` or `ambiguous`.
    An `ambiguous` outcome must never be reversed — poll it to a terminal
    status instead. See [Errors](/concepts/errors).
  </Accordion>

  <Accordion title="Reuse the idempotency key on retries" icon="rotate-cw">
    Never mint a new key to get past a failure. Same operation, same key —
    that is what stops one charge becoming two. See
    [Idempotency](/concepts/idempotency).
  </Accordion>

  <Accordion title="Verify amounts on settlement" icon="scale">
    Check `observed_amount_minor` and `observed_currency` against what you
    expected, not just `status`.
  </Accordion>

  <Accordion title="Deduplicate on event_id" icon="copy">
    Delivery is at-least-once and unordered. Track processed `event_id`s and
    use `sequence` to discard stale arrivals.
  </Accordion>
</AccordionGroup>

## Next

<Columns cols={2}>
  <Card title="Collections" icon="arrow-down-to-line" href="/guides/collections">
    Money in, end to end.
  </Card>

  <Card title="Payouts" icon="arrow-up-from-line" href="/guides/payouts">
    Money out, and the float model.
  </Card>

  <Card title="Authentication" icon="key" href="/authentication">
    The signing scheme in full.
  </Card>

  <Card title="API reference" icon="code" href="/api-reference/collections/charge-—-money-in">
    Every endpoint.
  </Card>
</Columns>
