Forhopp

ForhoppPay Connect - Complete API Documentation

Overview

ForhoppPay Connect is an enterprise-grade merchant payment integration platform that enables external businesses to accept payments through ForhoppPay. Similar to Stripe Connect or PayPal Commerce Platform, it provides:

  • API Keys - Publishable and secret key pairs for authentication
  • Charge API - Create, capture, and refund payments
  • Hosted Checkout - Secure payment pages at pay.forhopp.com
  • Webhooks - Real-time event notifications with signature verification
  • Sandbox Environment - Test integration without real payments
  • Multi-Currency Support - USD, EUR, GBP, CAD, AUD, JPY, CHF

Quick Start

1. Get Your API Keys

After enabling Connect on your ForhoppPay account, generate API keys from your dashboard:

Publishable Key: pk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
Secret Key:      sk_live_q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2
  • Publishable keys (pk_*) are safe for client-side code
  • Secret keys (sk_*) must be kept confidential on your server

2. Create a Charge

curl -X POST https://api.pay.forhopp.com/connect/v1/charges \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: unique-request-id" \
  -d '{
    "amount": 5000,
    "currency": "usd",
    "description": "Order #12345",
    "return_url": "https://yoursite.com/success",
    "cancel_url": "https://yoursite.com/cancel"
  }'

3. Redirect to Checkout

Use the checkout_url from the response to redirect your customer:

window.location.href = response.checkout_url;
// https://pay.forhopp.com/checkout/ch_1234567890abcdef

4. Handle Webhooks

Configure a webhook endpoint to receive payment notifications:

app.post('/webhooks/forhopppay', (req, res) => {
  const signature = req.headers['forhopppay-signature'];
  const payload = req.body;
  
  if (verifySignature(payload, signature, webhookSecret)) {
    switch (payload.type) {
      case 'charge.completed':
        // Payment successful - fulfill order
        break;
      case 'charge.failed':
        // Payment failed - notify customer
        break;
    }
  }
  res.status(200).send('OK');
});

Authentication

API Key Types

Key Type Format Usage
Live Publishable pk_live_[32 chars] Client-side, production
Live Secret sk_live_[32 chars] Server-side, production
Test Publishable pk_test_[32 chars] Client-side, sandbox
Test Secret sk_test_[32 chars] Server-side, sandbox

Making Authenticated Requests

Include your secret key in the Authorization header:

Authorization: Bearer sk_live_your_secret_key

Rate Limits

Environment Limit
Live (sk_live_*) 100 requests/second
Test (sk_test_*) 25 requests/second

Rate limit headers are included in every response:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1234567890

When exceeded, you'll receive HTTP 429 with a Retry-After header.


API Reference

Base URL

https://api.pay.forhopp.com/connect/v1

API Versioning

Specify version via header (optional):

ForhoppPay-Version: 2024-01-01

If not specified, the latest stable version is used.


Charges API

Create a Charge

Creates a new charge and returns a checkout URL.

POST /charges

Headers:

  • Authorization: Bearer sk_* (required)
  • Idempotency-Key: string (recommended)
  • Content-Type: application/json

Request Body:

Parameter Type Required Description
amount integer Yes Amount in cents (min: 50, max: 99999999)
currency string Yes ISO currency code: usd, eur, gbp, cad, aud, jpy, chf
description string No Description shown to customer (max 500 chars)
metadata object No Key-value pairs for your use
return_url string Yes Redirect URL after successful payment
cancel_url string No Redirect URL if customer cancels

Example Request:

{
  "amount": 5000,
  "currency": "usd",
  "description": "Order #12345",
  "metadata": {
    "order_id": "12345",
    "customer_email": "customer@example.com"
  },
  "return_url": "https://merchant.com/success",
  "cancel_url": "https://merchant.com/cancel"
}

Response (201 Created):

{
  "id": "ch_1234567890abcdef1234567890abcdef",
  "object": "charge",
  "amount": 5000,
  "currency": "usd",
  "status": "pending",
  "description": "Order #12345",
  "metadata": {
    "order_id": "12345",
    "customer_email": "customer@example.com"
  },
  "checkout_url": "https://pay.forhopp.com/checkout/ch_1234567890abcdef1234567890abcdef",
  "return_url": "https://merchant.com/success",
  "cancel_url": "https://merchant.com/cancel",
  "expires_at": 1234654290,
  "created": 1234567890,
  "livemode": true
}

Retrieve a Charge

GET /charges/{charge_id}

Response (200 OK):

{
  "id": "ch_1234567890abcdef1234567890abcdef",
  "object": "charge",
  "amount": 5000,
  "currency": "usd",
  "status": "captured",
  "description": "Order #12345",
  "payment_method": "card",
  "payment_method_details": {
    "card": {
      "brand": "visa",
      "last4": "4242",
      "exp_month": 12,
      "exp_year": 2025
    }
  },
  "fee": 175,
  "net": 4825,
  "refunds": [],
  "created": 1234567890,
  "captured_at": 1234567900,
  "livemode": true
}

List Charges

GET /charges?limit=10&status=captured

Query Parameters:

Parameter Type Description
limit integer Max results (1-100, default: 10)
status string Filter by status
created_after integer Unix timestamp filter
created_before integer Unix timestamp filter
starting_after string Cursor for pagination

Response:

{
  "object": "list",
  "data": [...],
  "has_more": true,
  "url": "/api/v1/connect/charges",
  "total_count": 150
}

Capture a Charge

Captures an authorized charge. Only charges with authorized status can be captured.

POST /charges/{charge_id}/capture

Request Body (optional for partial capture):

{
  "amount": 3000
}

Response (200 OK):

{
  "id": "ch_1234567890abcdef1234567890abcdef",
  "status": "captured",
  "amount": 5000,
  "fee": 175,
  "net": 4825,
  "captured_at": 1234567900
}

Refund a Charge

Creates a refund for a captured charge. Supports partial refunds.

POST /charges/{charge_id}/refunds

Request Body:

{
  "amount": 2500,
  "reason": "customer_request"
}

Response (201 Created):

{
  "id": "re_1234567890abcdef1234567890abcdef",
  "object": "refund",
  "charge": "ch_1234567890abcdef1234567890abcdef",
  "amount": 2500,
  "currency": "usd",
  "status": "succeeded",
  "reason": "customer_request",
  "created": 1234567950
}

Charge Lifecycle

┌─────────┐
│ PENDING │ ──────────────────────────────────────┐
└────┬────┘                                       │
     │ Customer completes payment                 │ 24h timeout
     ▼                                            ▼
┌────────────┐                              ┌─────────┐
│ AUTHORIZED │                              │ EXPIRED │
└─────┬──────┘                              └─────────┘
      │
      ├── Capture ──────────────────┐
      │                             ▼
      │                       ┌──────────┐
      │                       │ CAPTURED │
      │                       └────┬─────┘
      │                            │
      │                            ├── Partial Refund ──► PARTIALLY_REFUNDED
      │                            │
      │                            └── Full Refund ────► REFUNDED
      │
      ├── Void ─────────────────────────────────► VOIDED
      │
      └── 7 days without capture ───────────────► VOIDED

Status Definitions

Status Description
pending Charge created, awaiting customer payment
authorized Payment authorized, ready for capture
captured Payment captured, funds transferred
failed Payment failed
voided Charge cancelled before capture
refunded Fully refunded
partially_refunded Partially refunded
expired Checkout session expired (24h)
disputed Customer disputed the charge

Webhooks

Webhook Events

Event Description
charge.created Charge was created
charge.completed Payment was successful
charge.failed Payment failed
charge.refunded Charge was refunded (full or partial)
charge.disputed Customer disputed the charge
payout.created Payout was initiated
payout.completed Payout was successful
payout.failed Payout failed

Create Webhook Endpoint

POST /webhooks

Request:

{
  "url": "https://merchant.com/webhooks/forhopppay",
  "events": ["charge.completed", "charge.refunded", "charge.disputed"],
  "description": "Production webhook"
}

Response:

{
  "id": "we_1234567890abcdef",
  "object": "webhook_endpoint",
  "url": "https://merchant.com/webhooks/forhopppay",
  "secret": "whsec_1234567890abcdef1234567890abcdef",
  "events": ["charge.completed", "charge.refunded", "charge.disputed"],
  "status": "enabled",
  "created": 1234567890
}

Important: The secret is only shown once at creation. Store it securely.

Webhook Payload Format

{
  "id": "evt_1234567890abcdef12345678",
  "object": "event",
  "type": "charge.completed",
  "created": 1234567890,
  "livemode": true,
  "data": {
    "object": {
      "id": "ch_1234567890abcdef1234567890abcdef",
      "object": "charge",
      "amount": 5000,
      "currency": "usd",
      "status": "captured",
      "payment_method": "card",
      "metadata": {
        "order_id": "12345"
      }
    }
  }
}

Signature Verification

Every webhook includes a ForhoppPay-Signature header:

ForhoppPay-Signature: t=1234567890,v1=abc123def456...

Verification Steps:

  1. Extract timestamp (t) and signature (v1) from header
  2. Check timestamp is within 5 minutes of current time
  3. Compute expected signature: HMAC-SHA256(timestamp + "." + payload, webhook_secret)
  4. Compare signatures using constant-time comparison

Java Example:

public boolean verifyWebhookSignature(String payload, String signature, String secret) {
    String[] parts = signature.split(",");
    String timestamp = null;
    String v1Signature = null;
    
    for (String part : parts) {
        if (part.startsWith("t=")) {
            timestamp = part.substring(2);
        } else if (part.startsWith("v1=")) {
            v1Signature = part.substring(3);
        }
    }
    
    // Check timestamp is within 5 minutes
    long timestampLong = Long.parseLong(timestamp);
    if (System.currentTimeMillis() / 1000 - timestampLong > 300) {
        return false;
    }
    
    // Compute expected signature
    String signedPayload = timestamp + "." + payload;
    String expectedSignature = HmacUtils.hmacSha256Hex(secret, signedPayload);
    
    return MessageDigest.isEqual(
        expectedSignature.getBytes(StandardCharsets.UTF_8),
        v1Signature.getBytes(StandardCharsets.UTF_8)
    );
}

Retry Schedule

Failed webhook deliveries are retried automatically:

Attempt Delay
1 1 minute
2 5 minutes
3 30 minutes
4 2 hours
5 24 hours

After 5 consecutive failures, the endpoint is marked as failing.


API Key Management

Create API Key

POST /keys
Authorization: Bearer {jwt_token}

Request:

{
  "name": "Production Key",
  "environment": "live"
}

Response:

{
  "id": "key_1234567890abcdef",
  "object": "api_key",
  "name": "Production Key",
  "publishable_key": "pk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
  "secret_key": "sk_live_q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2",
  "environment": "live",
  "created": 1234567890
}

Important: The secret_key is only shown once. Store it securely.

List API Keys

GET /keys
Authorization: Bearer {jwt_token}

Revoke API Key

DELETE /keys/{key_id}
Authorization: Bearer {jwt_token}

Sandbox Environment

Test API Keys

Use test keys (sk_test_*, pk_test_*) to test your integration without processing real payments.

Test Card Numbers

Card Number Behavior
4242424242424242 Successful payment
4000000000000002 Card declined
4000000000009995 Insufficient funds
4000000000000069 Expired card
4000000000000127 Incorrect CVC
4000000000000119 Processing error

Test PayPal Accounts

Email Behavior
test-buyer@forhopppay.com Successful payment
test-declined@forhopppay.com Payment declined

Sandbox Behavior

  • No real payments are processed
  • Webhooks are delivered to test endpoints
  • Data is isolated from production
  • Lower rate limits (25 req/sec)

Fee Structure

Platform Fees

Fee Type Amount
Transaction Fee 2.9% + $0.30
Currency Conversion 1% above mid-market rate
Dispute Fee $15 (refunded if won)

Fee Calculation Example:

Charge Amount: $50.00 (5000 cents)
Percentage Fee: 5000 × 0.029 = 145 cents
Fixed Fee: 30 cents
Total Fee: 175 cents ($1.75)
Net Amount: 4825 cents ($48.25)

Payouts

Schedule Timing
Daily 00:00 UTC
Weekly Monday 00:00 UTC
Monthly 1st of month 00:00 UTC

Minimum payout threshold: $25


Supported Currencies

Currency Code Minimum Charge
US Dollar USD $0.50
Euro EUR €0.50
British Pound GBP £0.50
Canadian Dollar CAD CA$0.50
Australian Dollar AUD A$0.50
Japanese Yen JPY ¥50
Swiss Franc CHF CHF 0.50

Error Handling

Error Response Format

{
  "error": {
    "type": "invalid_request_error",
    "code": "parameter_invalid",
    "message": "The amount must be a positive integer in cents.",
    "param": "amount",
    "doc_url": "https://docs.forhopppay.com/errors/parameter_invalid"
  }
}

Error Types

Type HTTP Status Description
invalid_request_error 400 Invalid parameters
authentication_error 401 Invalid API key
authorization_error 403 Insufficient permissions
resource_not_found 404 Resource doesn't exist
idempotency_error 409 Idempotency key conflict
rate_limit_error 429 Too many requests
api_error 500 Internal error
gateway_error 502 Payment gateway error

Common Error Codes

Code Description
parameter_missing Required parameter not provided
parameter_invalid Parameter value is invalid
amount_too_small Amount below minimum (50 cents)
amount_too_large Amount exceeds maximum
currency_unsupported Currency not supported
charge_already_captured Charge already captured
charge_already_refunded Charge fully refunded
charge_expired Checkout session expired
refund_exceeds_charge Refund amount too large
api_key_revoked API key has been revoked

Idempotency

Use the Idempotency-Key header to safely retry requests:

POST /charges
Idempotency-Key: order_12345_attempt_1
  • Keys are unique per merchant
  • Keys expire after 24 hours
  • Duplicate requests return the original response
  • Use unique keys for each distinct operation

Security Best Practices

  1. Never expose secret keys - Keep sk_* keys on your server only
  2. Verify webhook signatures - Always validate the ForhoppPay-Signature header
  3. Use HTTPS - All API communication must use TLS 1.2+
  4. Implement idempotency - Use Idempotency-Key for all POST requests
  5. Rotate keys regularly - Generate new keys periodically
  6. Monitor for anomalies - Set up alerts for unusual activity

Rate Limits & Quotas

Resource Limit
API Keys per Merchant 10
Webhook Endpoints per Merchant 10
Requests per Second (Live) 100
Requests per Second (Test) 25
Maximum Charge Amount $999,999.99
Checkout Session Duration 24 hours
Authorized Charge Hold 7 days

SDK & Integrations

JavaScript SDK

<script src="https://js.forhopppay.com/v1/forhopppay.js"></script>
<script>
  const forhopppay = new ForhoppPay('pk_live_xxx');
  
  // Redirect to checkout
  forhopppay.redirectToCheckout({
    chargeId: 'ch_xxx'
  });
</script>

npm Package

npm install @forhopppay/js
import { ForhoppPay } from '@forhopppay/js';

const forhopppay = new ForhoppPay('pk_live_xxx');

WordPress/WooCommerce

Install the ForhoppPay plugin from the WordPress plugin directory or download from your merchant dashboard.

Shopify

Install the ForhoppPay app from the Shopify App Store.


Support


Last Updated: March 2026

Was this page helpful?

Edit this page