MidgardPay API

← Back

Overview

MidgardPay is a deterministic, audit-friendly crypto payment gateway.

Base URL
https://midgardpay.com/api
Environment
Sandbox / Production
Content-Type
application/json

Authentication

All requests must include your API credentials using headers.

curl https://midgardpay.com/api/payments/create \
  -X POST \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: YOUR_API_KEY" \
  -H "X-API-SECRET: YOUR_API_SECRET"
<?php

$headers = [
  "Content-Type: application/json",
  "X-API-KEY: YOUR_API_KEY",
  "X-API-SECRET: YOUR_API_SECRET"
];

Create Payment

Use this endpoint to create a payment intent and redirect the buyer to the MidgardPay hosted payment page. The customer will choose the network and currency on the payment page, not in this API call.

POST /api/payments/create

Request body

Send a JSON payload with the amount in USD and your unique order ID.

{
  "amount_usd": 5.00,
  "order_id": "ORDER-1234" // optional, string, max 64 chars
}

Amount rules

  • amount_usd is always interpreted in USD.
  • The buyer pays in crypto, but the merchant must always send the amount in USD.
  • Only numeric values are accepted.
  • Up to 2 decimal places are allowed (e.g. 15, 15.0, 15.00).
  • Values with more than 2 decimals will be rejected.
  • order_id: optional, string, max 64 chars.

All payment amounts sent to the API are always interpreted in USD. The buyer will later choose a crypto network and pay the equivalent amount in crypto, but the merchant must always send the amount in USD with up to 2 decimal places.

PHP Example

<?php

$apiKey    = "YOUR_API_KEY";
$apiSecret = "YOUR_API_SECRET";

$payload = [
    "amount_usd" => 5.00,
    "order_id"   => "ORDER-1234"
];

$ch = curl_init("https://midgardpay.com/api/payments/create");

curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Content-Type: application/json",
        "X-API-KEY: $apiKey",
        "X-API-SECRET: $apiSecret"
    ],
    CURLOPT_POSTFIELDS => json_encode($payload)
]);

$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true);

// Redirect user to MidgardPay payment page
if (!empty($data["data"]["payment_url"])) {
    header("Location: " . $data["data"]["payment_url"]);
    exit;
}

echo "Payment creation failed.";

Response

{
  "success": true,
  "data": {
    "payment_url": "https://midgardpay.com/pay/API-XXXX",
    "payment_intent": "xxxxxxxxxxxxxxxx"
  }
}

Callback (Webhook)

When a payment is fully confirmed on the blockchain, MidgardPay sends a POST request to the callback URL defined in your merchant dashboard. This allows your system to automatically mark the order as paid. The callback includes an HMAC signature that must be verified to ensure the request is authentic.

Callback Payload

MidgardPay sends the following fields to your callback URL using POST.


order=ORDER-1234
intent=702ec4bdbeaa32b145b26b8c6b5f94fb32d9f7b5a30843d049c6ff9462722cb3
status=confirmed
hash=4f8c0d9e0e5b1c...
    

The hash field is an HMAC-SHA256 signature used to verify authenticity.

HMAC Signature

MidgardPay generates the signature using the following string:


$order|$intent|$status
    

And signs it using your api_secret:


hash_hmac('sha256', "$order|$intent|$status", $merchantSecret);
    
<?php
// Read POST data
$order  = $_POST['order']  ?? '';
$intent = $_POST['intent'] ?? '';
$status = $_POST['status'] ?? '';
$hash   = $_POST['hash']   ?? '';

// Merchant secret from MidgardPay dashboard
$merchantSecret = 'api_secret_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';

// Build signature string
$string = "$order|$intent|$status";

// Generate expected HMAC
$expectedHash = hash_hmac('sha256', $string, $merchantSecret);

// Invalid signature
if (!hash_equals($expectedHash, $hash)) {
    http_response_code(400);
    echo json_encode([
        'success' => false,
        'error'   => 'invalid_signature'
    ]);
    exit;
}

// Signature valid → update your order status here
// Example:
// markOrderAsPaid($order);

// Return success
http_response_code(200);
echo json_encode(['success' => true]);
  

Bulk Status (Confirmed Payments)

Fetch multiple confirmed payments in a single request. Use this endpoint if you are not using webhooks (callbacks) and need to poll payment results via cron.

If a callback URL is configured, MidgardPay will automatically notify your system, and using this endpoint is usually not necessary.

💡 Tip: Use this endpoint in a cron job instead of querying each payment individually.

Recommended: Run every 60–120 seconds to stay within rate limits.

The API returns confirmed payments from the last 4 hours, ordered by creation time.

POST /api/payments/status-bulk

No request body is required.

This endpoint returns up to 50 confirmed payments associated with your API key.

Response (No Payments)

{
  "success": true,
  "data": {
    "payments": [],
    "count": 0
  }
}

Returned when no confirmed payments are found in the given time window.

Response (Multiple Payments)

{
  "success": true,
  "data": {
    "payments": [
      {
        "intent": "81f7c92c...",
        "order_id": "newORDER-6907",
        "status": "confirmed",
        "amount": "5.00",
        "created_at": "2026-03-28 19:31:14",
        "expires_at": "2026-03-28 19:36:18"
      }
    ],
    "count": 1
  }
}

Rate Limit

Max 6 requests per minute per merchant. Exceeding this limit will return a retry hint in seconds.

{
  "success": false,
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Bulk limit reached (max 6 requests per minute)",
    "meta": {
      "retry_after_seconds": 46,
      "reset_at": "2026-03-28 19:35:19"
    }
  }
}

Response Fields

  • intent – Unique payment identifier.
  • order_id – Your order ID.
  • status – Always confirmed (only confirmed payments are returned).
  • amount – Amount in USD.
  • created_at – UTC time.
  • expires_at – UTC expiration.

Example Usage (PHP)

<?php
// No request body required
$apiKey    = "YOUR_API_KEY";
$apiSecret = "YOUR_API_SECRET";

$ch = curl_init("https://midgardpay.com/api/payments/status-bulk");

curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Content-Type: application/json",
    "X-API-KEY: $apiKey",
    "X-API-SECRET: $apiSecret",
]);

curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true);

if (!empty($data['data']['payments'])) {
    foreach ($data['data']['payments'] as $payment) {
        if ($payment['status'] === 'confirmed') {
            echo "PAID: " . $payment['order_id'] . PHP_EOL;
        }
    }
}

Status Fetch (No Callback)

If you prefer not to use webhooks, or if your system cannot receive incoming POST requests, you can fetch the payment status directly from MidgardPay using the Status API. This method allows your server to periodically check whether a payment has been completed.

POST /api/payments/status

Request

You can query the status using either payment_ref or order_id. Only one of them is required.

{
  "payment_ref": "PAY-123456789",
  "order_id": "ORDER-1234"
}

Response

The API returns the latest known status of the payment. If the buyer has not yet selected a network or started the payment, the status will indicate that the intent exists but no payment has been initiated.

{
  "success": true,
  "data": {
    "payment_ref": "PAY-123456789",
    "order_id": "ORDER-1234",
    "status": "confirmed",
    "amount": 5.00,
    "currency": "TON",
    "network": "ton",
    "expires_at": "2026-03-27 19:23:00"
  }
}

If the buyer has not yet selected a network, the API returns:

{
  "success": true,
  "data": {
    "status": "waiting_for_payment",
    "message": "payment intent created, waiting for buyer"
  }
}

Status Values

  • intent – Payment intent created, buyer has not selected a network yet.
  • waiting_for_payment – Buyer selected a network, waiting for deposit.
  • confirming – Transaction detected, waiting for blockchain confirmations.
  • confirmed – Payment fully confirmed.
  • expired – Payment window expired.

Example Status Check (PHP)

This example shows how to fetch the payment status using payment_ref or order_id.

<?php
$apiKey    = "api_key_XXXXXXXXXXXXXXXXXXXXXXXX";
$apiSecret = "api_secret_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";

$postData = [
    'payment_ref' => "PAY-123456789",
    // or: 'order_id' => "ORDER-1234"
];

$ch = curl_init("https://midgardpay.com/api/payments/status");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Content-Type: application/json",
    "X-API-KEY: $apiKey",
    "X-API-SECRET: $apiSecret",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postData));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true);

$status = $data['data']['status'] ?? 'unknown';

// Example usage:
// if ($status === 'confirmed') markOrderAsPaid();

Response Examples

These examples demonstrate all possible responses returned by the Status API. Use them to correctly handle each payment state in your system.

Confirmed

{
  "http_code": 200,
  "response": {
    "success": true,
    "data": {
      "intent": "5e4eb2cf8ebf747a4802cbb133b4d174b5c2519c59bfcfe4aadcaaa0f7655998",
      "order_id": "ORDER-1774672392",
      "status": "confirmed",
      "amount": "18.99",
      "expires_at": "2026-03-28 04:38:17",
      "created_at": "2026-03-28 04:33:12"
    }
  }
}
  

Pending

{
  "http_code": 200,
  "response": {
    "success": true,
    "data": {
      "intent": "9495205964fe22dd3f1e7488c93caa7a3b620160c103584afade30201163b907",
      "order_id": "newORDER-2702",
      "status": "pending",
      "amount": "5.00",
      "expires_at": "2026-03-28 07:15:31",
      "created_at": "2026-03-28 07:10:31"
    }
  }
}
  

Cancelled / Expired

{
  "http_code": 200,
  "response": {
    "success": true,
    "data": {
      "intent": "9495205964fe22dd3f1e7488c93caa7a3b620160c103584afade30201163b907",
      "order_id": "newORDER-2702",
      "status": "cancelled",
      "amount": "5.00",
      "expires_at": "2026-03-28 07:15:22",
      "created_at": "2026-03-28 07:10:31"
    }
  }
}
  

Waiting for Payment

{
  "http_code": 200,
  "response": {
    "success": true,
    "data": {
      "status": "waiting_for_payment",
      "message": "payment intent created, waiting for buyer"
    }
  }
}
  

Payment Not Found

{
  "http_code": 404,
  "response": {
    "success": false,
    "error": {
      "code": "payment_not_found",
      "message": "payment not found",
      "meta": []
    }
  }
}
  

Errors

MidgardPay uses a consistent error format for all API endpoints. Every error response includes an error code, a human‑readable message, and an empty meta object reserved for future use.

Error format

{
  "success": false,
  "error": {
    "code": "string",
    "message": "string",
    "meta": {}
  }
}

Common error codes

  • missing_api_credentials – Returned when X-API-KEY or X-API-SECRET is missing.
  • invalid_api_credentials – Returned when the provided API key or secret is incorrect or inactive.
  • missing_identifier – Returned when neither intent nor order_id is provided.
  • payment_not_found – Returned when no matching payment exists for the authenticated merchant.
  • invalid_amount – Returned when amount_usd is not numeric or has more than 2 decimals.
  • internal_error – Returned when an unexpected server error occurs.

Example: Missing API credentials

{
  "success": false,
  "error": {
    "code": "missing_api_credentials",
    "message": "unauthorized",
    "meta": {}
  }
}

Example: Payment not found

{
  "success": false,
  "error": {
    "code": "payment_not_found",
    "message": "payment not found",
    "meta": {}
  }
}