Greenlane

Developers

Integrate in an afternoon

Our SDK does the heavy lifting: one function on your server, one script tag in your cashier, one webhook. No extra API call before your page loads. Test keys arrive the moment you sign up, before approval.

greenlane.js · ~11 KB gzipped · no dependenciesNode, PHP, Python, Java

Quickstart

Four steps to your first payment

1

Sign a customer token on your server

When you render your cashier, sign the player's email, the amount and your cashier's address with your token signing secret (dashboard → Integration). It's a local signature, not an API call, and it's valid for 5 minutes.

Keep the secret on your server. Set emailVerified only if the player confirmed their email with you. Anyone who changes the email, amount or country gets a grey button.

If the player has excluded themselves from gambling with you, add customer.self_excluded: true. Greenlane then sends them receipts only: no reminders, announcements or top-up prompts.

amount is in the minor units of currency (cents for EUR, whole yen for JPY). Leave currency out and we use your default payment currency (EUR if none is set). It must be enabled for you, or the button is grey.

Node: download greenlane-server.js (no dependencies). Other languages use any JWT library with HS256.

// greenlane-server.js from this page
const { signCustomerToken } = require("./greenlane-server");

const customerToken = signCustomerToken({
  secret: process.env.GREENLANE_SIGNING_SECRET,
  origin: "https://pay.your-casino.com", // your cashier
  customer: {
    email: player.email,                 // hashed for you
    id: player.id, country: "DE",
    emailVerified: player.emailVerified
  },
  amount: 10000, currency: "EUR"         // minor units; leave currency out for your default
});
2

Add the button to your cashier

Load greenlane.js and pass your publishable key and the token. The button turns green or grey as it loads, opens our hosted checkout, and handles popups, redirects and mobile for you. Card details are typed on Greenlane's checkout, never on your page, and Greenlane sends them through its own API to the card processor. That card flow is in Greenlane's PCI DSS scope; your site doesn't see, send or store card data.

Currencies. Greenlane.currencies(publishableKey) (or GET /v1/button/currencies?pk=) returns your allowed currencies and default, read live, so one added or removed later appears or disappears with no code change. Never hard-code the list. One currency per token; a currency you aren't allowed greys the button. The customer chooses the payment method in Greenlane's checkout.

If the player changes the amount, sign a new token and call button.update({ customerToken, amount, currency }) (or just update({ amount }) with getToken). Call it from the amount field's input event with a short pause (about 400 ms), not from change: change fires when the player clicks the button, and the re-check would start just as they click. One token per button: two buttons on one page need two tokens. A grey button never says why, so show your other payment methods.

After a redirect checkout, the return URL carries the payment id. Anyone can type that URL, so never credit from it: credit only from the signed webhook (step 3).

Prefer to host it yourself? Download greenlane.js. The file already points at Greenlane, so it works from your own domain. Re-download it when we announce updates.

<select id="currency"></select>
<div id="greenlane-button"></div>
<script src="https://staging.greenlane.cash/greenlane.js"></script>
<script>
  const button = Greenlane.button("#greenlane-button", {
    publishableKey: "pk_test_…",
    customerToken: "<from step 1>",
    amount: 10000,                       // minor units of the currency
    currencyPicker: "#currency",         // optional: a <select> filled live with your allowed currencies
    getToken: ({ amount, currency }) =>  // your server signs a new token per amount / currency (step 1)
      fetch("/greenlane/token", { method: "POST", headers: { "content-type": "application/json" },
        body: JSON.stringify({ amount, currency }) }).then(r => r.json()).then(j => j.token),
    theme: "dark",
    email: "<player's email>",        // optional: pre-fills checkout
    onSuccess: ({ paymentId }) => showPending(paymentId),
    onState: ({ state }) => state === "not_available" && showOtherMethods()
  });

  // These two are YOURS to write (greenlane.js does not define them).
  function showPending(paymentId) { /* "Payment received, crediting…" until the webhook arrives */ }
  function showOtherMethods() { /* show your other payment methods */ }

  // Return page after a redirect checkout: anyone can type the URL, so never show success
  // or credit from it. Credit only from the signed payment.succeeded webhook.
  const back = Greenlane.handleReturn();
  if (back) showPending(back.paymentId);
</script>

No JavaScript? Drop in a tag and the SDK mounts itself:

<div data-greenlane-button
     data-publishable-key="pk_test_…"
     data-customer-token="<from step 1>"
     data-amount="10000"
     data-currency="EUR"></div>
3

Credit the player from the webhook

We POST signed events to your endpoint (dashboard → Integration → Webhooks). Credit the player's balance when payment.succeeded arrives, never from the browser.

Other events: payment.failed, refund.created (status refunded or pending), refund.succeeded and refund.failed for pending ones, dispute.opened, payout.sent, checkout.unavailable. Reply 2xx within 10 s; we retry 8 times over about two days.

// Express. Greenlane-Signature: t=<unix>,v1=<hex>
app.post("/hooks/greenlane", express.raw({ type: "application/json" }), (req, res) => {
  const [t, v1] = req.get("Greenlane-Signature").split(",").map(p => p.split("=")[1]);
  const expected = crypto.createHmac("sha256", process.env.GREENLANE_WEBHOOK_SECRET)
    .update(t + "." + req.body).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
  if (!fresh || expected.length !== v1.length ||
      !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))) return res.sendStatus(400);

  const event = JSON.parse(req.body);
  if (event.type === "payment.succeeded") {
    const { customer_ref, amount, bonus_amount } = event.data;
    creditPlayer(customer_ref, amount + bonus_amount); // once per event id
  }
  res.sendStatus(200);
});
4

Verify your domain and go live

The button only runs on domains you've proved you own. Add them in dashboard → Integration → Domains, then add a DNS record or upload a file. We approve each domain and re-check it daily.

Test mode also works on localhost without verification.

_greenlane.your-casino.com  TXT  "greenlane-verify=<token>"
  • ✓ Business verified (KYB) and approved
  • ✓ Cashier domain verified
  • ✓ Live signing secret on your server, pk_live_… in the page
  • ✓ Webhook endpoint answering 2xx and checking signatures
  • ✓ Payout wallet verified

SDK reference

greenlane.js

Options

OptionWhat it does
publishableKeyYour pk_test_… or pk_live_… key. Required.
customerTokenThe token from step 1. Required. A token works once: each button, and each button.update(), needs a newly signed one.
amountAmount in cents. Must match the token.
theme"light" or "dark"
size"default" (64 px) or "compact" (48 px)
checkout"auto" (popup on desktop, redirect on mobile), "popup" or "redirect"
returnUrlWhere a redirect checkout comes back to. Defaults to the current page. Must be on your verified domain.
emailOptional. The player's email, to pre-fill checkout. It must be the email in the token.
timeoutMsGrey if we don't answer in time. Default 3000.
behaviourfalse switches off fraud signals for this button (see below).

Callbacks and methods

NameWhen
onState(state)Every check. state.state is eligible, eligible_returning or not_available.
onStart()Checkout opened.
onSuccess({ paymentId })The customer paid. Wait for the webhook before crediting.
onCancel()The checkout popup was closed without paying.
onUnavailable()We couldn't take this payment after all. Show other methods.
button.update({…})New token or amount: re-checks and repaints.
button.destroy()Removes the button.
Greenlane.consent({ behaviour })For your consent manager: { behaviour: false } stops fraud signals on this page.
Greenlane.handleReturn()On your return page after a redirect: { paymentId, status } or null. It is read from the URL, so anyone can type one: show "checking your payment" and credit only from the signed webhook.

Using React or Vue? Listen for DOM events on the element instead: greenlane:state, greenlane:success, greenlane:cancel.

Optional · off until Greenlane approves you

App top-ups and deposit reminders

A player links their account with you once. After that they can top it up from the Greenlane app. Greenlane asks you before every charge, and credits you with a signed webhook. If you don't confirm the credit within 15 minutes, the player is refunded in full automatically. Turn it on under Integration → App top-ups.

1 · Link the account

On your server, sign a link token with your signing secret. It works like the customer token, with aud: "greenlane-link", a lifetime of 300 s or less, and licensed: true once you've checked you hold a licence for the player's country. customer.id (your player id) and customer.email_verified: true are required.

const { signLinkToken } = require("@greenlane/js/server");
const linkToken = signLinkToken({
  secret: process.env.GREENLANE_SIGNING_SECRET,
  origin: "https://pay.your-casino.com", licensed: true,
  customer: { id: player.id, email: player.email,
              emailVerified: true, country: "DE" }
});

In the page, load /sdk/link.js and call it from a click:

Greenlane.link({ publishableKey: "pk_live_…", linkToken })
  .then(r => save(r.linkId))
  .catch(e => show(e.message));

The player confirms with a passkey or an email code. The Greenlane email must match the one you signed. You get a link.created webhook, and link.unlinked when either side unlinks.

2 · Say yes or no before each charge

Option A · check URL. We POST, signed with Greenlane-Signature like webhooks:

{ "link_id": "lnk_…", "customer_ref": "player-77",
  "amount": 5000, "currency": "EUR", "request_id": "…" }

Answer within 3 seconds with { "allow": true } or { "allow": false, "reason": "LIMIT" }. The reason is one of LIMIT, SELF_EXCLUDED, KYC, CLOSED or OTHER. No answer, an error or anything else counts as a no. SELF_EXCLUDED and CLOSED also unlink the account.

Option B · status updates. Send POST /v1/merchant/player-status with your secret key whenever something changes, within 60 s:

{ "customer_ref": "player-77", "can_deposit": true,
  "remaining_limit_minor": 20000, "period": "MONTH",
  "self_excluded": false, "kyc_ok": true }

A status older than 24 hours counts as a no.

3 · Credit from the webhook

After payment we send topup.succeeded with an Idempotency-Key header (the top-up id). It carries topup_id, payment_id, link_id, customer_ref, amount and currency. Credit the player, then reply 2xx with your transaction id:

{ "merchant_txn_id": "dep_93812" }

A 2xx without merchant_txn_id isn't a credit, and we retry. To refuse it, reply 2xx with { "status": "rejected" } and we refund straight away; any other answer is retried. If you haven't credited within 15 minutes, we refund and send topup.refunded. Never credit the same topup_id twice.

Links API and reconciliation

DELETE /v1/links/{id}Unlink.
POST /v1/links/{id}/pause · /resumeStop or restart top-ups for one player.
POST /v1/links/revoke{ customer_ref, reason }. Unlinks everywhere. SELF_EXCLUDED blocks re-linking.
POST /v1/links/unblock{ customer_ref }. Lifts that block.
GET /v1/topups?date=YYYY-MM-DD&format=csvThe day's top-ups for your books.

Deposit reminders

If you load greenlane-observe.js, it pings /v1/presence at most every 5 minutes. The ping carries your publishable key and a random device id, and nothing about the page. Greenlane may then send a linked player a reminder, but only if you, Greenlane and the player have all switched reminders on. Reminders are capped at 1 a day per merchant and 3 a week per player. They never go out during quiet hours or a session, after 3 deposits in 24 h, or after a decline.

Test mode

Put these in customer.id: test_allow, test_limit, test_self_excluded, test_timeout (the check), and test_credit_reject, test_credit_timeout (the credit, which refunds).

Privacy

What greenlane.js collects

To tell people from automated scripts and card testers, greenlane.js measures how the Greenlane button is used, never what is typed. It turns this into a short list of numbers in the browser and sends only those numbers. It stops when the button is clicked, or after 10 minutes.

Sent (numbers only)

TimingTime from the button appearing to the first input and to the click; time the tab was hidden.
PointerMouse, touch or pen; number of moves; how straight and how even the path was; pauses; big jumps; where on the button the click landed (relative to its centre); how long it was held; whether the browser marked the click as real.
Aiming at the buttonAt the click only, from the last 2 seconds of pointer movement kept in memory: how long the pointer rested on the button, how often it crossed the button's edge, how many corrective moves it made, how much it trembled and how its timing compares with how fast people aim. The positions themselves never leave the page.
Scroll and focusNumber of scrolls, how far down the page, how often the window lost focus.
Touch devicesNumber of touches and how much their size varied; how many motion-sensor readings and how much they varied, only if the browser already allows it. We never ask for permission.
BrowserWhether the browser says it is automated, a count of headless-browser hints, whether its timer is coarse.
Where fromThe referring site's domain (e.g. google.com) and whether the page was opened, reloaded or reached with Back.

Never collected

ContentWhat anyone types, field values, key names or codes, clipboard contents.
RecordingsMouse coordinates, session replays, screenshots, the page's HTML.
Sensitive fieldsNothing at all from password fields, card fields (autocomplete="cc-…") or anything marked data-greenlane-ignore.
Full addressesFull URLs or query strings of your pages or the referrer.

Used only for fraud prevention and the green/grey decision. Not used for marketing, not sold, not shown to other merchants. Kept 90 days, then deleted; deleted earlier when the customer asks us to erase their data. Without signals (ad-blocker, old browser, consent off) the button works exactly as before.

Optional, off by default

All-pages mode. Load <script src="https://staging.greenlane.cash/greenlane-observe.js"></script> on every page and call GreenlaneObserve({ publishableKey }). It counts pages in this visit and their kind (home, game, promo, deposit, account, other) in the player's own browser tab, using the paths you set in Dashboard → Integration. Never full URLs.

Email field timing. Mark your own sign-up or login email input with data-greenlane-field="email". We then time keystrokes and count pastes there. Never what is typed. Switch it on in Dashboard → Integration.

For your privacy policy LEGAL REVIEW

“We use Greenlane to help prevent fraud. Greenlane's script measures how the checkout button is used (for example timing and pointer movement patterns, never what you type) to tell people from automated scripts.”

Switch it off from your consent manager with Greenlane.consent({ behaviour: false }), or per button with behaviour: false. Your QA team's emails can be added under Dashboard → Integration → Our testers so they're never scored.

Testing

Build against test mode

Use your test signing secret and pk_test_… key. Test payments move no real money and become available on your plan's schedule (T+N), like live ones. localhost works without domain verification.

At checkout, customers confirm their email with a 6-digit code or a passkey. In test mode the code is also shown on screen, so you don't need a real inbox. A device that has confirmed the email isn't asked again for 7 days.

Test cardResult
4242 4242 4242 4242Approved
4000 0000 0000 0002Declined · insufficient funds
4000 0000 0000 3220Asks for 3-D Secure
4000 0000 0000 9995Declined · stolen card
These cards work with Greenlane's built-in test processor. If your test mode is connected to the Paysecure sandbox, only Paysecure's own sandbox test cards approve: ask your Greenlane contact for them. Any future expiry date and any CVC. Prefer a server call to decide eligibility yourself? POST /v1/eligibility with your secret key still works.

Get your test keys now

Build against the sandbox today. Go live after approval.