> For the complete documentation index, see [llms.txt](https://docs.loomgate.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.loomgate.io/en/getting-started/quickstart.md).

# Quickstart

From API key to the first payment on your website.

This guide uses `loomgate.js` (a script served by Loomgate) in the browser and Node.js on the server. Any other server language can call the API over HTTP, as in the `curl` example.

{% hint style="info" %}
Want working code right away? The [loomgate-examples repository](https://github.com/loomgroup/loomgate-examples) has a complete checkout page for each way to integrate (JS SDK, the `loomgate.js` script tag, the UMD build, React): pick the example that matches your stack. The [playground](https://examples.loomgate.io) lets you run each step with your own keys (payments use real money).
{% endhint %}

{% stepper %}
{% step %}

### Get your API keys

Sign in to the [merchant dashboard](https://app.loomgate.io), open [**API key**](https://app.loomgate.io/api-keys) and create a new key pair.

* The **secret key** (`sk_live_…`) is shown **only once**. Save it right away in an environment variable on your server, for example `LOOMGATE_SECRET_KEY`. Never send it to the browser or commit it to source control.
* The **publishable key** (`pk_live_…`) is used in the browser and can be viewed again at any time.
  {% endstep %}

{% step %}

### Create a payment on your server

When the buyer reaches your checkout page, your server creates a payment for the order. `items` is required; the buyer, order reference and shipping address can be sent now or added later with an update, but they must be present before the buyer pays (see [Payment details](/en/concepts/payment-details.md)).

{% tabs %}
{% tab title="curl" %}

```bash
curl https://api.loomgate.io/partner/v1/payment_intents \
  -H "Authorization: Bearer $LOOMGATE_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042" \
  -d '{
    "amount": 10000,
    "currency": "usd",
    "items": [{ "name": "T-shirt", "quantity": 2, "unit_amount": 4500 }],
    "shipping_amount": 1000,
    "order_reference": "1042",
    "buyer": { "name": "Jane Doe" },
    "shipping_details": {
      "name": "Jane Doe",
      "address": { "line1": "1 Market St", "city": "San Francisco", "state": "CA", "postal_code": "94105", "country": "US" }
    },
    "metadata": { "order_id": "1042" }
  }'
```

{% endtab %}

{% tab title="Node.js" %}

```js
import { createLoomgateServerClient } from '@loompay/loomgate-js-sdk/server';

const loomgate = createLoomgateServerClient(process.env.LOOMGATE_SECRET_KEY, {
  apiBaseUrl: 'https://api.loomgate.io',
});

const intent = await loomgate.paymentIntents.create(
  {
    amount: 10000,
    currency: 'usd',
    items: [{ name: 'T-shirt', quantity: 2, unit_amount: 4500 }],
    shipping_amount: 1000,
    order_reference: '1042',
    buyer: { name: 'Jane Doe' },
    shipping_details: {
      name: 'Jane Doe',
      address: { line1: '1 Market St', city: 'San Francisco', state: 'CA', postal_code: '94105', country: 'US' },
    },
    metadata: { order_id: '1042' },
  },
  { idempotencyKey: 'order-1042' },
);

// Send intent.client_secret to the browser and save intent.id on the order.
```

{% endtab %}
{% endtabs %}

Response (abridged; actual fees depend on your fee schedule):

```json
{
  "id": "lg_pi_4Qx9ZcM2LwT8rKv1bN3a",
  "object": "payment_intent",
  "status": "pending",
  "currency": "usd",
  "amount": 10000,
  "fee_buyer": 320,
  "amount_total": 10320,
  "fee_bearer": "buyer",
  "client_secret": "lg_cs_4Qx9ZcM2LwT8rKv1bN3a_…"
}
```

`client_secret` is only returned when the payment is created. Show the buyer exactly `amount_total`: that is the amount the card will be charged.
{% endstep %}

{% step %}

### Show the card form in the browser

Load `loomgate.js`, mount the card form and the branding element (required), then call `confirm()` when the buyer clicks Pay.

```html
<div id="loomgate-payment"></div>
<div id="loomgate-branding"></div> <!-- required -->
<p id="total"></p>
<button id="pay" disabled>Pay</button>

<script src="https://api.loomgate.io/partner/v1/loomgate.js"></script>
<script>
  (async () => {
    const loomgate = await window.Loomgate.loadLoomgate('pk_live_…');
    const session = loomgate.payment({ clientSecret: 'lg_cs_…' }); // from your server

    session.on('change', ({ complete, amountTotal, currency }) => {
      document.getElementById('pay').disabled = !complete;
      document.getElementById('total').textContent =
        new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amountTotal / 100);
    });
    await session.mount({ payment: '#loomgate-payment', branding: '#loomgate-branding' });

    document.getElementById('pay').addEventListener('click', () => {
      // Call confirm() directly in the click handler, without awaiting anything before it.
      session
        .confirm({
          billingDetails: {
            email: 'buyer@example.com',
            name: 'Jane Doe',
            address: { line1: '1 Market St', city: 'San Francisco', state: 'CA', postal_code: '94105', country: 'US' },
          },
        })
        .then((result) => {
          if (result.error) alert(result.error.message);
          else location.assign('/orders/1042/thanks');
        });
    });
  })();
</script>
```

See [Payment form (loomgate.js)](/en/integrations/browser.md) for details.
{% endstep %}

{% step %}

### Receive the webhook and fulfill the order

Create a webhook endpoint in the dashboard (the **Webhook** section) pointing to your server and note its **endpoint ID** (`we_…`). When a payment succeeds, Loomgate sends a `payment_intent.succeeded` event:

```js
import express from 'express';
import { createLoomgateServerClient, verifyWebhook } from '@loompay/loomgate-js-sdk/server';

const loomgate = createLoomgateServerClient(process.env.LOOMGATE_SECRET_KEY, {
  apiBaseUrl: 'https://api.loomgate.io',
});
const endpointId = process.env.LOOMGATE_WEBHOOK_ENDPOINT_ID; // we_…
let publicKeys = {};

async function refreshKeys() {
  const { data } = await loomgate.webhookSigningKeys.list();
  publicKeys = Object.fromEntries(data.map((key) => [key.kid, key.public_key_pem]));
}

const app = express();

// Needs the raw body: don't use express.json() for this route.
app.post('/webhooks/loomgate', express.raw({ type: 'application/json' }), async (req, res) => {
  const verify = () => verifyWebhook(req.body, req.get('Loomgate-Signature'), { endpointId, publicKeys });
  let result = verify();
  if (!result.ok && result.code === 'unknown_key') {
    await refreshKeys(); // new signing key: reload once
    result = verify();
  }
  if (!result.ok) return res.status(400).send(result.code);

  const event = JSON.parse(req.body.toString('utf8'));
  if (event.type === 'payment_intent.succeeded') {
    const payment = event.data.object;
    await markOrderPaid(payment.order_reference, payment.id); // skip if event.id was already processed
  }
  res.sendStatus(200);
});
```

See [Verify signatures](/en/webhooks/verify-signatures.md) for PHP, Python and how to test your code.
{% endstep %}

{% step %}

### Try a real payment

Create a small payment (minimum total 1.00 USD or 1.00 EUR), pay it with your own card, and check that:

* the webhook reaches your server and the order is marked as paid;
* the payment shows up in the **Giao dịch** (Payments) section of the dashboard.

Then refund it from the dashboard or with the [Refunds API](/en/api-reference/refunds.md). Transaction fees are not refunded.
{% endstep %}
{% endstepper %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.loomgate.io/en/getting-started/quickstart.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
