إنتقل إلى المحتوى الرئيسي

Create shareable payment links. A link can be single-use or reusable, fixed-amount or open-amount (customer enters the amount, e.g. tips/donations).

POST/payment-links

Create a payment link

AuthYour secret API key as a Bearer token: Authorization: Bearer spi_live_… (or spi_test_…)payment_links:write

Creates a payment link and returns its full record, including payPageUrl — the shareable URL you send to the customer. Omit amount to create an open-amount link where the customer enters the amount at checkout.

Request body

titlestringrequired

Short title shown to the customer on the checkout page.

amountobjectoptional

Amount in IQD. Omit for an open-amount link (customer picks the amount — tip jars / donations).

isReusablebooleanoptional

If true, the link can be paid more than once. Default: single-use.

expiresAtstring (date-time)optional

Optional expiry. ISO-8601 (YYYY-MM-DD or full timestamp).

customerIdstringoptional

Optional existing customer id.

customerNamestringoptional
customerPhonestringoptional

A valid Iraqi phone number in E.164 form.

{
  "title": "Order #1042",
  "amount": "50000",
  "isReusable": false,
  "customerName": "Ahmed Ali",
  "customerPhone": "+9647801234567"
}

Responses

Payment link created

POST/payment-links/{id}/disable

Disable a payment link

AuthYour secret API key as a Bearer token: Authorization: Bearer spi_live_… (or spi_test_…)payment_links:write

Disables a payment link so it can no longer be paid. Returns the updated record with status: DISABLED.

Parameters

idstringpathrequired

Resource id.

Responses

Disabled payment link

Invoices

Create itemized invoices with line items, tax, and discounts. The invoice is issued immediately and the response returns a hosted pay-page URL (payPage.url) — share that link with your customer so they can pay on the SwiftPayIQ checkout page.

GET/invoices

List invoices

AuthYour secret API key as a Bearer token: Authorization: Bearer spi_live_… (or spi_test_…)invoices:read

Returns a paginated list of invoices for the active business in the current mode, with optional status, customer, date-range, and sort filters.

Parameters

pageintegerquery

Page number (1-based).

limitintegerquery

Items per page (max 100).

statusDRAFT | ISSUED | SENT | PAID | PARTIALLY_PAID | OVERDUE | CANCELLEDquery

Filter by status.

customerIdstringquery

Filter by linked customer id.

fromstring (date-time)query

Inclusive lower bound on createdAt (ISO-8601).

tostring (date-time)query

Inclusive upper bound on createdAt (ISO-8601).

sortBycreatedAt | updatedAt | dueDate | issuedAt | total | numberquery
sortDirasc | descquery

Responses

Paginated invoices

POST/invoices

Create an invoice

AuthYour secret API key as a Bearer token: Authorization: Bearer spi_live_… (or spi_test_…)invoices:write

Creates an invoice from line items and issues it immediately. SwiftPayIQ does not message the customer for you over this API — there is no WhatsApp/SMS channel. Instead the response returns payPage.url, the hosted checkout page; deliver that link to your customer however you like (your own email/SMS, a redirect, a QR code, etc.). API-created invoices come back with status: SENT, source: API, and deliveryChannel: API.

Request body

customerIdstringoptional

Optional existing customer id.

customerNamestringrequired

Snapshot kept on the invoice even if the customer row changes later.

customerPhonestringrequired

A valid Iraqi phone number in E.164 form.

itemsobject[]required
taxAmountobjectoptional

Extra tax on top of the subtotal. Default "0".

discountobjectoptional

Fixed IQD discount. Mutually exclusive with discountPercentage.

discountPercentagestringoptional

Percentage discount (0–99.99, up to 2 decimals). Mutually exclusive with discount.

dueDatestring (date-time)optional

ISO-8601 (YYYY-MM-DD or full timestamp).

notesstringoptional
{
  "customerName": "API-generated customer",
  "customerPhone": "+9647701112233",
  "items": [
    {
      "description": "SaaS subscription — monthly",
      "quantity": 1,
      "unitPrice": "45000"
    }
  ],
  "discount": "5000",
  "taxAmount": "0",
  "notes": "Issued via server-to-server integration"
}

Responses

Invoice created and issued

{
  "invoice": {
    "id": "cmptoobsi0007xnlvtyx4iux2",
    "number": "INV-000002",
    "slug": "1tinjslkqp",
    "isLive": false,
    "customerId": null,
    "customerName": "API-generated customer",
    "customerPhone": "+9647701112233",
    "status": "SENT",
    "source": "API",
    "deliveryChannel": "API",
    "sentAt": "2026-05-31T11:15:33.387Z",
    "subtotal": "45000",
    "discount": "5000",
    "discountPercentage": null,
    "taxAmount": "0",
    "customerFeeAmount": "0",
    "total": "40000",
    "amountPaid": "0",
    "feeRate": "0",
    "feeAmount": "0",
    "customerFeeShare": "0.0000",
    "netAmount": "40000",
    "dueDate": null,
    "issuedAt": "2026-05-31T11:15:33.387Z",
    "paidAt": null,
    "notes": "Issued via server-to-server integration",
    "createdByType": "API_KEY",
    "createdAt": "2026-05-31T11:15:33.426Z",
    "updatedAt": "2026-05-31T11:15:33.426Z",
    "items": [
      {
        "id": "cmptoobso0008xnlvz938stgm",
        "productId": null,
        "description": "SaaS subscription — monthly",
        "quantity": 1,
        "unitPrice": "45000",
        "lineTotal": "45000",
        "position": 0
      }
    ],
    "payments": [],
    "collections": []
  },
  "payPage": {
    "url": "https://swiftpayiq.com/pay?slug=1tinjslkqp"
  }
}
GET/invoices/{id}

Fetch an invoice

AuthYour secret API key as a Bearer token: Authorization: Bearer spi_live_… (or spi_test_…)invoices:read

Parameters

idstringpathrequired

Resource id.

Responses

Invoice

PATCH/invoices/{id}

Update an invoice

AuthYour secret API key as a Bearer token: Authorization: Bearer spi_live_… (or spi_test_…)invoices:write

Partially updates an invoice. Only DRAFT invoices can be edited. Sending items replaces the full line-item list.

Parameters

idstringpathrequired

Resource id.

Request body

customerIdstringoptional
customerNamestringoptional
customerPhonestringoptional

A valid Iraqi phone number in E.164 form.

itemsobject[]optional
taxAmountstringoptional

An amount in IQD (no decimals), serialized as an integer string to preserve precision.

discountstringoptional

An amount in IQD (no decimals), serialized as an integer string to preserve precision.

discountPercentagestringoptional
dueDatestring (date-time)optional
notesstringoptional

Responses

Updated invoice

DELETE/invoices/{id}

Delete an invoice draft

AuthYour secret API key as a Bearer token: Authorization: Bearer spi_live_… (or spi_test_…)invoices:write

Deletes a DRAFT invoice. Issued or paid invoices cannot be deleted — cancel them instead.

Parameters

idstringpathrequired

Resource id.

Responses

Deleted

POST/invoices/{id}/cancel

Cancel an invoice

AuthYour secret API key as a Bearer token: Authorization: Bearer spi_live_… (or spi_test_…)invoices:write

Cancels an issued or sent invoice. Returns the updated record with status: CANCELLED.

Parameters

idstringpathrequired

Resource id.

Responses

Cancelled invoice

Partner Connections

Connecting a merchant to your platform. You call this once per merchant, with your own partner key, to receive that merchant's API key. Everything after that uses the merchant key against the Payment Links and Invoices endpoints.

POST/partner/connections/exchange

Swap a merchant's one-time code for their API key

AuthYour partner key as a Bearer token: Authorization: Bearer spp_live_…

The second half of the connection handover.

The merchant authorises your platform inside their SwiftPayIQ portal and is redirected back to you carrying a short-lived code. That code grants nothing on its own — your server swaps it here, authenticated with your partner key, and receives the merchant's own API key in return.

The merchant key is returned exactly once. It is stored hashed and can never be retrieved again, so persist it before you respond to the request. Lose it and the merchant has to authorise you again.

Single use, and codes expire five minutes after they are issued.

A code does not always come back

If the merchant connects a business they already had — rather than one created through the setup page you sent them to — the link is recorded as pending review and waits for a SwiftPayIQ admin to approve it. Their consent is real and dated; what is missing is our decision.

In that case no code is issued, and the merchant returns to your redirect address without a ?code= parameter. This is deliberate: an established merchant's business is not handed to a platform on the strength of one button press.

Handle the no-code return. Treat it as "waiting on SwiftPayIQ", not as a failure and not as a finished connection. Send the merchant back through the setup page and you will get partner.link_already_pending_review — the request is already on file.

Request body

codestringrequired

The one-time code from the merchant redirect.

Responses

Connected. Store apiKey now — it is never shown again.

GET/partner/connections/pending

Merchants an admin approved that you have not collected yet

AuthYour partner key as a Bearer token: Authorization: Bearer spp_live_…

The other half of the review flow.

When a merchant connects a business they already had, the link waits for a SwiftPayIQ admin. That decision happens out of band, days after the merchant consented, so there is no redirect to carry a code back to you. This is where your server finds out.

Each entry carries a fresh one-time code. Swap it at /partner/connections/exchange exactly as you would one that arrived by redirect.

Codes are minted per call and live five minutes, so call this when you are ready to exchange rather than on a slow timer. An entry disappears once you hold a key for that merchant, so a run that fails halfway is safe to repeat — you will simply be offered the ones you did not finish.

An empty array means nothing is waiting. Poll it a few times a day, or when the PARTNER_CONNECTION_READY notification reaches your SwiftPayIQ inbox.

Responses

Approved merchants awaiting collection. Empty when there are none.

Partner Merchants

Onboarding a merchant from your own signup flow. Creates an unclaimed account and returns a link for the merchant to claim it themselves.

POST/partner/merchants

Create a referred merchant from your own signup flow

AuthYour partner key as a Bearer token: Authorization: Bearer spp_live_…

For a platform whose merchants sign up on your side rather than ours.

Creates an unverified account, a DRAFT business and a pending link, and returns a claim URL to send the merchant.

What it does not do: issue a password, send a one-time code, or earn you anything yet. The merchant opens the claim URL, sets their own password and verifies their own phone — and that verification is the only thing that activates the link and starts commission. You cannot consent on their behalf.

An existing SwiftPayIQ phone number is refused rather than claimed: invite that merchant through the connection flow instead.

Request body

phonestringrequired

The merchant's own phone, E.164.

namestringrequired
emailstringoptional
attestedConsentbooleanrequired

You are asserting the merchant asked you to do this. Recorded on the consent trail against your partner account, with the key on the audit entry. Required.

businessobjectrequired

Responses

Created. Send claimUrl to the merchant — nothing happens until they use it.

POST/partner/onboarding-sessions

Send a merchant to a setup page we host

AuthPartnerApiKey

For a platform that would rather not build sign-up, one-time codes and document upload itself.

Returns a single-use URL. Open it for the merchant in a new tab; every screen that touches a password or a one-time code stays on our pages, so the merchant's credentials never pass through your servers.

The page reads the merchant's own records each time rather than storing a step number, so somebody who stops halfway through uploading documents and comes back later lands on exactly the same screen.

Register a return address first. Without one this call fails with partner.no_redirect_uri — a journey that ends nowhere is not a finished journey. Ask our team to set it for your account.

When the merchant finishes we send them to that address with ?code=, the same one-time code described on POST /partner/connections/exchange, swapped the same way from your server.

Two ways the journey can end

A business created through this page is yours to connect: the merchant presses connect and comes back to you with ?code=.

A business the merchant already had goes to review instead. They come back with no ?code=, and a SwiftPayIQ admin decides. Build for both endings — the second one is the normal case for any merchant who was already trading with us.

Request body

phonestringrequired

The merchant's own phone, E.164. The one-time code goes here, which is what stops you completing this on their behalf.

prefillobjectoptional

Anything you already know, to save them typing: trade name, governorate. Convenience only — they can change all of it and none of it is trusted.

Responses

Link created.

Webhooks

We post these to the URL registered for your account. Reply 2xx quickly and do the work afterwards — a slow handler looks the same to us as a broken one.

EVENTpayment.succeeded

payment.succeeded

Sent when a payment completes successfully. This is the event most integrations care about — fulfil the order when you receive it (after verifying the signature).

How webhooks are delivered

SwiftPayIQ sends an HTTP POST to the endpoint URL you configured in the dashboard, with a JSON body of the shape:

{ "event": "payment.succeeded", "data": { /* … */ } }

Headers on every delivery:

HeaderValue
Content-Typeapplication/json
X-SwiftPay-Signaturet=<unix-seconds>,v1=<hex-hmac>

Your endpoint should respond with any 2xx status as quickly as possible. A non-2xx response (or a timeout — the request is aborted after 10 seconds) is treated as a failure and retried.

Verifying the signature

Every delivery is signed with the signing secret of your webhook (shown when you create or rotate it in the dashboard; it starts with whsec_). Verify it before trusting the payload:

  1. Read the X-SwiftPay-Signature header and parse the t (timestamp) and v1 (signature) values.
  2. Reject the request if t is more than 5 minutes away from your current time (replay protection).
  3. Build the signed string: "{t}.{rawRequestBody}" — use the raw, unparsed request body bytes exactly as received.
  4. Compute HMAC-SHA256(signingSecret, signedString) and hex-encode it.
  5. Compare it to v1 using a constant-time comparison. If they match, the request is authentic.
// Node.js (Express) example
const crypto = require('crypto');

function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map(p => p.split('=')));
  const t = parts.t, v1 = parts.v1;
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // 5 min
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(v1, 'hex'));
}

> Capture the raw request body for signature verification — re-serializing parsed JSON will change the bytes and the signature will not match.

Retries

If your endpoint doesn't return 2xx, delivery is retried up to 6 attempts total (5 retries) with increasing back-off: 1 min → 5 min → 30 min → 2 hours → 8 hours (about 10.5 hours end-to-end). A 4xx response other than 408/429 is treated as a permanent failure and is not retried. Make your handler idempotent — the same event may arrive more than once. You can inspect delivery history and re-send from the dashboard.

Payload

eventpayment.succeeded | payment.failed | payment.refunded | invoice.paid | payment_link.paidrequired

The event type.

dataobjectrequired

The payment that triggered the event. Amounts are IQD integer strings.

{
  "event": "payment.succeeded",
  "data": {
    "paymentId": "9b2c0e1a-1f3d-4a55-8c2e-1a2b3c4d5e6f",
    "businessId": "3a1d…",
    "isLive": true,
    "amount": "50000",
    "status": "SUCCEEDED",
    "provider": "ALQASEH",
    "gatewayTxnId": "alq_8f21c…",
    "invoiceId": null,
    "paymentLinkId": "7c9e…",
    "customerId": null,
    "customerPhone": "+9647801234567"
  }
}
EVENTpayment.failed

payment.failed

Sent when a payment attempt fails. Same delivery, signing, and retry rules as payment.succeeded.

Payload

eventpayment.succeeded | payment.failed | payment.refunded | invoice.paid | payment_link.paidrequired

The event type.

dataobjectrequired

The payment that triggered the event. Amounts are IQD integer strings.

{
  "event": "payment.failed",
  "data": {
    "paymentId": "…",
    "status": "FAILED",
    "amount": "50000",
    "isLive": true,
    "provider": "ALQASEH",
    "customerPhone": "+9647801234567",
    "invoiceId": null,
    "paymentLinkId": "7c9e…",
    "customerId": null,
    "gatewayTxnId": "alq_…",
    "businessId": "3a1d…"
  }
}
EVENTpayment.refunded

payment.refunded

Sent when a previously successful payment is refunded. Same delivery, signing, and retry rules as payment.succeeded.

Payload

eventpayment.succeeded | payment.failed | payment.refunded | invoice.paid | payment_link.paidrequired

The event type.

dataobjectrequired

The payment that triggered the event. Amounts are IQD integer strings.

{
  "event": "payment.refunded",
  "data": {
    "paymentId": "…",
    "status": "REFUNDED",
    "amount": "50000",
    "isLive": true,
    "provider": "ALQASEH",
    "customerPhone": "+9647801234567",
    "invoiceId": null,
    "paymentLinkId": "7c9e…",
    "customerId": null,
    "gatewayTxnId": "alq_…",
    "businessId": "3a1d…"
  }
}
EVENTinvoice.paid

invoice.paid

Sent when an invoice is fully paid. Only fired for payments linked to an invoice — data.invoiceId is populated. Same delivery, signing, and retry rules as payment.succeeded.

Payload

eventpayment.succeeded | payment.failed | payment.refunded | invoice.paid | payment_link.paidrequired

The event type.

dataobjectrequired

The payment that triggered the event. Amounts are IQD integer strings.

{
  "event": "invoice.paid",
  "data": {
    "paymentId": "…",
    "status": "SUCCEEDED",
    "amount": "85000",
    "isLive": true,
    "provider": "ALQASEH",
    "customerPhone": "+9647801234567",
    "invoiceId": "f01a…",
    "paymentLinkId": null,
    "customerId": null,
    "gatewayTxnId": "alq_…",
    "businessId": "3a1d…"
  }
}