Skip to main content
After a customer completes a payment, Quidkey delivers the result to your backend via an HTTPS webhook. This guide covers webhook setup, signature verification, fee processing, and a production QA checklist.

Redirect Handling

After bank authentication, the customer is redirected to the URL you specified when creating the payment request:
  • Success: redirected to success_url
  • Failure / Cancel: redirected to failure_url
Do not rely solely on the redirect to confirm payment. Always verify payment status via the webhook. Redirects can fail or be interrupted.

Webhook Setup

Register and Obtain a Signing Secret

1

Register your webhook URL

curl -X POST 'https://core.quidkey.com/api/v1/webhooks' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "webhook_url": "https://api.yoursite.com/webhooks/quidkey"
  }'
The response confirms the URL has been registered.
2

Generate the signing secret

curl -X POST 'https://core.quidkey.com/api/v1/webhooks/secret' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
The secret is returned once. Store it safely in a secure vault (AWS Secrets Manager, HashiCorp Vault, etc.).
See the Generate Webhook Secret API for complete details and interactive playground.
3

Revoke the secret (if needed)

Roll or revoke the secret during incident response:
curl -X POST 'https://core.quidkey.com/api/v1/webhooks/secret/revoke' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'

Webhook Payload

Quidkey sends a Stripe-style envelope so existing tooling can be reused.
{
  "id": "evt_28b2d68f",
  "object": "event",
  "created": 1716148300,
  "type": "quidkey.payment_request.succeeded",
  "data": {
    "object": {
      "id": "pr_782516093",
      "amount": 2550,
      "currency": "EUR",
      "status": "succeeded",
      "test": true,
      "metadata": {
        "order_id": "ORD-123",
        "payment_token": "ptok_..."
      },
      "fees": {
        "total_fees": 2.50,
        "fees_currency": "EUR",
        "fees_breakdown": [
          {
            "id": "fee_percentage_123",
            "type": "percentage",
            "amount": 1.50,
            "currency": "EUR",
            "rate_type": "domestic_percent_fee",
            "rate_value": 1.5,
            "notes": "1.5% fee on 25.50 EUR"
          }
        ]
      }
    }
  }
}
Fee information is only included for successful payments (status: "succeeded"). Failed or cancelled payments do not include fees.

HTTP Headers

HeaderPurpose
X-Signaturet=<unix-ts>,v1=<hex-hmac>
X-TimestampUnix epoch seconds
X-Client-IdYour client_id
The HMAC is SHA-256 over "${timestamp}.${raw_body}", keyed by your webhook signing secret.

Verify Signatures

The X-Signature header lets you confirm that the webhook came from Quidkey and that the payload was not tampered with.
const sig = req.get('x-signature');
const event = stripe.webhooks.constructEvent(
  req.rawBody,
  sig,
  process.env.QUIDKEY_WEBHOOK_SECRET
);

Process Webhook Events

app.post('/webhooks/quidkey', (req, res) => {
  const event = stripe.webhooks.constructEvent(
    req.body,
    req.headers['x-signature'],
    webhookSecret
  );

  if (event.type === 'quidkey.payment_request.succeeded') {
    const payment = event.data.object;

    // Store payment details
    await updatePayment(payment.metadata.order_id, {
      status: payment.status,
      amount: payment.amount,
      currency: payment.currency
    });

    // Process fee information if present
    if (payment.fees) {
      await storeFeeInformation({
        orderId: payment.metadata.order_id,
        totalFees: payment.fees.total_fees,
        feesCurrency: payment.fees.fees_currency,
        feeBreakdown: payment.fees.fees_breakdown
      });
    }

    // Process reward information if present
    if (payment.rewards) {
      await distributeRewards({
        orderId: payment.metadata.order_id,
        extraRewards: payment.rewards.extra_rewards,
        totalRewards: payment.rewards.total_rewards
      });
    }
  }

  res.status(200).send('OK');
});
Retry behavior: Quidkey retries for up to 3 days with exponential backoff until your endpoint returns any 2xx status. Events may be delivered out of order or duplicated, so de-duplicate using the top-level id field.

Fee Handling

Quidkey automatically calculates and applies fees for successful transactions. Fee information is included in webhook payloads for merchant accounting and billing reconciliation. Fee types:
  • Percentage fees: based on transaction amount (e.g., 1.5% of €100 = €1.50)
  • Fixed fees: flat rate per transaction (e.g., €1.00 per transaction)
  • Currency-specific: fees are calculated in the same currency as the transaction
Each fee in the fees_breakdown array contains:
{
  "id": "fee_unique_identifier",
  "type": "percentage",
  "amount": 1.50,
  "currency": "EUR",
  "rate_type": "domestic_percent_fee",
  "rate_value": 1.5,
  "notes": "1.5% fee on 25.50 EUR"
}

QA Checklist Before Going Live

  • Serve checkout over HTTPS (wallets such as Apple Pay require it)
  • Use live Stripe keys in production mode (if using Stripe alongside)
  • Ask Quidkey for your production merchant_id and iframe URL
  • Store webhook secret in secure vault
  • Verify that only one payment method can be selected at any time
  • Confirm purchase button enables/disables correctly with bank selection
  • Test post-purchase redirect flows for success and failure
  • Verify dynamic height adjustments work smoothly
  • Verify amount updates work before payment initiation
  • Confirm updates are blocked after customer selects a bank
  • Test error handling for expired tokens
  • Ensure iframe refreshes correctly after updates
  • Verify webhook signature validation works
  • Test webhook retry and idempotency handling
  • Verify successful payments include fee information
  • Ensure your system correctly stores fee breakdown data

Next Steps

Embedded Flow Overview

Back to the Embedded Flow overview

Embed the Checkout

Iframe setup, Stripe mutual exclusion, and purchase button routing

Webhook API Reference

Full webhook endpoint documentation

Hosted Checkout

Collect payments via a hosted checkout page instead