NOVA: ₹0 brokerage for your first 30 days, with a full-service house behind you.

Pay ₹0. Miss nothing.

One Platform – For Every Investor's Trading Journey.

Whether you’re a serious trader, active options trader, occasional investor, woman building financial independence, or a long-term mutual fund investor — Nova gives you the technology, market access, support to invest and trade your way.

₹0 brokerage for the first 30 days from account activation; flat ₹9 per executed order thereafter. Statutory and clearing charges apply. See all charges.

Nova ₹0 brokerage
The ₹0 list

What costs ₹0, what is free for 30 days, and what comes after

No fine print to hunt for. Every other charge is listed further down this page.

Nova by Maitra Wealth
₹0

Always

  • Account opening
  • Demat AMC, free for lifetime
  • IPO applications
  • Call & trade
  • Auto square-off
  • No volume commitments
₹0

Brokerage for your first 30 days

  • Equity intraday and delivery (stocks / ETFs)
  • Equity futures & options
  • Commodity futures & options (MCX)
  • The 30 days start when your account is activated for trading
₹9

Then, flat per executed order

  • Same price in every segment
  • Per order, not per lot
  • No plan to choose, it starts automatically
  • IPO applications stay at ₹0

Statutory levies (STT/CTT, stamp duty, exchange & SEBI charges, GST) and broker clearing charges apply as listed under Other charges. Offer terms subject to change.

The Nova advantage

₹0 to begin. Nothing left out of the app.

The Nova app includes all of the following features at no extra charge.

Market analytics powered by NXTOPTION

Built for how you trade

Option traders

Strategy Builder & OI tools
1

Every leg of a multi-leg strategy at ₹9 brokerage. Strategy Builder and OI tools included.

Intraday traders

Flat ₹9 · 30 days at ₹0
2

The more orders you place, the more a flat ₹9 — and 30 days at ₹0 — saves you.

Commodity traders

Gold, silver & crude on MCX
3

Gold, silver, crude and more on MCX, from a house with commodities in its name.

Long-term investors

SIPs, IPOs, funds & bonds
4

Stock SIPs, IPOs, mutual funds and bonds from the same account.

New to markets

Start small, keep more
5

Learn with small trades while brokerage stays low: ₹0 for 30 days, then a flat ₹9.

WHAT YOU KEEP

Every rupee of brokerage you don't pay stays in your trading capital.

A min charge of ₹20 per executed order on intraday, F&O and commodities. Pick how many orders you place in a day.

Assumes 22 trading days in a month. Brokerage only; statutory and clearing charges are not included.

At Nova, first 30 days₹0
At ₹20 per order, 1 month₹2,200
You save in your first month of trading₹2,200110 executed orders across 22 trading days, all at ₹0 brokerage
From day 31 at ₹9: ₹990/month — still ₹1,210 less brokerage than a ₹20 broker, every month.
Compare & save

What an active trader saves with Nova

Illustration for a trader placing 2 executed orders a day, 22 trading days a month (44 orders), one lot per order. Brokerage only, before GST, after the 30-day free period. Statutory and clearing charges are not included; comparison rates are illustrative.

NOVA · ₹9₹396per month
AT ₹20 PER EXECUTED ORDER₹880per month
Nova saves ₹5,808 a year
At ₹40 PER LOT₹1,760per month
Nova saves ₹16,368 a year
Nova · Pricing

Trade free for 30 days. Then just ₹9 per order.

₹0 brokerage on every trade for your first 30 days. After that, a flat ₹9 per executed order across every segment — equity, F&O, commodities. No per-lot charges, and every other charge is listed on this page.

SEBI Registered · INZ000074139Serving investors since 2012
Account activatedInstant paperless Demat & Trading account opening
Day 1 – Day 30Welcome offer
₹0brokerage
on all trades
Day 31 onwards
₹9flat per executed order
all segments

Free period starts when your account is activated for trading. Statutory levies (STT/CTT, stamp duty, exchange & SEBI charges, GST) and broker clearing charges apply as listed below.

₹0Account opening
₹0Brokerage for 30 days
₹9Flat per order, after
₹0AMC Lifetime Free
Detailed breakdown

One price. Every segment.

Whether you buy one lot or trade up to the exchange freeze quantity, you pay the same ₹9 per executed order.

SegmentFirst 30 daysFrom day 31How it works
Equity Intraday (Stocks / ETFs)₹0₹9Per executed order
Equity Delivery (Stocks / ETFs)₹0₹9Per executed order
Equity Futures — Nifty, Bank Nifty, Sensex, Stock Futures₹0₹9Flat per order, any quantity up to freeze limit
Equity Options — Index & Stock Options₹0₹9Flat per order — not per lot
Commodity Futures (MCX)₹0₹9Gold, silver, crude oil, natural gas & more
Commodity Options (MCX)₹0₹9Flat per order
IPO applications₹0₹0Apply via UPI mandate from the app
Other charges

Broker charges

Charges set by Maitra Wealth. Brokerage is on the table above; these are the others.

ChargeApplies toRate
Account openingNew Nova accounts₹0
Demat AMCAnnual maintenance₹0 — free for lifetime
Clearing chargesCash / Intraday / Delivery₹75 per crore
Clearing chargesFutures₹200 per crore
Clearing chargesOptions₹5,000 per crore
DP charges (CDSL)Per scrip, on sell from demat₹50 per scrip
Margin pledge / unpledgePer ISIN₹30 + GST / per ISIN
MTF interestMargin Trading Facility (optional)18% p.a.

Statutory & exchange charges

Levied by the government, exchanges and regulator, and the same at every broker in India. Maitra does not control or profit from these levies.

ChargeApplies toRate
STT / CTTEquity, F&O, CommoditiesAs notified by Govt. of India
Stamp dutyBuy side, all segmentsAs per Indian Stamp Act
Exchange transaction chargesNSE / BSE / MCXAs levied by the exchange
SEBI turnover feeAll segments₹10 per crore
GSTBrokerage, DP, exchange & SEBI charges18%

Full tariff schedule available on request. Try the brokerage calculator →

FAQs

Nova pricing, answered

Still have a question? Call 044-45630555 or write to info@maitrawealth.com.

The offer
Is brokerage really zero for 30 days?

Yes. Every executed order in every segment carries ₹0 brokerage for 30 days from the date your account is activated for trading.

Which segments does the offer cover?

What happens after 30 days?

Is ₹9 charged per lot or per order?

Charges
Is there an account opening fee or AMC?

Do I still pay statutory charges during the offer?

Platform & tools
Do I pay extra for TradingView charts or option tools?

About us
What is Nova?

What does NOVA stand for?

Who is Maitra Wealth?

Not ready to open an account?

Get a callback in your language

Leave your number and our team will call you back to answer your questions about Nova. Prefer to chat? Message us on WhatsApp.

Protected by Cloudflare Turnstile. Cloudflare's Privacy Policy and Terms apply.

Pay ₹0, Miss nothing.

Start with ₹0 brokerage for 30 days.

Open your account online in minutes. ₹0 account opening, lifetime-free Demat AMC, TradingView charts and the full Nova toolkit included.

/* Maitra Nova page scripts. Single source: index.html no longer carries an inline copy. */ (function () { if (window.__novaReady) return; window.__novaReady = 1; var $ = function (i) { return document.getElementById(i); }; var CFG = window.NOVA_CONFIG || {}; var reduceMotion = window.matchMedia && matchMedia('(prefers-reduced-motion:reduce)').matches; /* 0. Analytics (loads only if a GA4 ID is set in NOVA_CONFIG) */ function track(name, params) { try { if (window.gtag) window.gtag('event', name, params || {}); } catch (e) {} } if (CFG.ga4 && /^G-[A-Z0-9]+$/.test(CFG.ga4)) { var g = document.createElement('script'); g.async = true; g.src = 'https://www.googletagmanager.com/gtag/js?id=' + CFG.ga4; document.head.appendChild(g); window.dataLayer = window.dataLayer || []; window.gtag = function () { window.dataLayer.push(arguments); }; window.gtag('js', new Date()); window.gtag('config', CFG.ga4); } document.addEventListener('click', function (e) { var a = e.target.closest && e.target.closest('[data-ev]'); if (a) track(a.getAttribute('data-ev'), { link_url: a.href || '' }); }); /* 1. WhatsApp buttons: shown only when a number is configured */ (function () { var n = (CFG.whatsapp || '').replace(/\D/g, ''); if (n.length === 10) n = '91' + n; if (n.length < 11) return; var href = 'https://wa.me/' + n + '?text=' + encodeURIComponent(CFG.whatsappText || 'Hi, I would like to know more about Maitra Nova.'); [].forEach.call(document.querySelectorAll('[data-wa]'), function (a) { a.href = href; a.hidden = false; }); })(); /* 2. Brokerage calculator chips */ (function () { var f = function (v) { return '₹' + v.toLocaleString('en-IN'); }; var b = document.querySelectorAll('.chip'); if (!b.length) return; function set(n) { var o = n * 22; $('c20').textContent = f(o * 20); $('save1').textContent = f(o * 20); $('orders').textContent = o; $('c9').textContent = f(o * 9); $('saveM').textContent = f(o * 11); [].forEach.call(b, function (x) { x.classList.toggle('on', +x.dataset.n === n); }); } [].forEach.call(b, function (x) { x.addEventListener('click', function () { set(+x.dataset.n); }); }); })(); /* 3. Mobile menu */ (function () { var h = document.querySelector('.hdr'), b = document.querySelector('.burger'); if (!h || !b) return; function close() { h.classList.remove('open'); b.setAttribute('aria-expanded', 'false'); } b.addEventListener('click', function () { var o = h.classList.toggle('open'); b.setAttribute('aria-expanded', o ? 'true' : 'false'); }); [].forEach.call(h.querySelectorAll('.nav a'), function (a) { a.addEventListener('click', close); }); window.addEventListener('resize', function () { if (window.innerWidth > 960) close(); }); })(); /* 4. In-page anchors: smooth scroll, never navigate away (even if the host adds a tag) */ document.addEventListener('click', function (e) { var a = e.target.closest && e.target.closest('a[href^="#"]'); if (!a) return; var id = a.getAttribute('href').slice(1); var t = id && $(id); if (!t) return; e.preventDefault(); t.scrollIntoView({ behavior: reduceMotion ? 'auto' : 'smooth', block: 'start' }); try { history.replaceState(null, '', location.pathname + location.search + '#' + id); } catch (err) {} }); /* 5. Back to top */ (function () { var b = $('backToTop'); if (!b) return; b.addEventListener('click', function () { window.scrollTo({ top: 0, behavior: reduceMotion ? 'auto' : 'smooth' }); }); if ('IntersectionObserver' in window) { /* Sentinel 400px down the page: no scrollY read, so no forced reflow on load. */ var s = document.createElement('div'); s.setAttribute('aria-hidden', 'true'); s.style.cssText = 'position:absolute;top:400px;left:0;width:1px;height:1px;pointer-events:none;visibility:hidden'; document.body.appendChild(s); new IntersectionObserver(function (es) { var e = es[es.length - 1]; b.classList.toggle('show', !e.isIntersecting && e.boundingClientRect.top < 0); }).observe(s); } else { var t = function () { b.classList.toggle('show', window.scrollY > 400); }; window.addEventListener('scroll', t, { passive: true }); t(); } })(); /* 6. FAQ: one open at a time */ (function () { var all = document.querySelectorAll('#faqs details'); [].forEach.call(all, function (d) { d.addEventListener('toggle', function () { if (d.open) [].forEach.call(all, function (o) { if (o !== d) o.open = false; }); }); }); })(); /* 7. Timeline */ (function () { var rows = document.querySelectorAll('.bt-row'), bt = $('bt'), fill = $('btFill'); if (!bt || !fill) return; if ('IntersectionObserver' in window) { var io = new IntersectionObserver(function (es) { es.forEach(function (e) { if (e.isIntersecting) { e.target.classList.add('in'); io.unobserve(e.target); } }); }, { threshold: 0.2 }); [].forEach.call(rows, function (r) { io.observe(r); }); } else { [].forEach.call(rows, function (r) { r.classList.add('in'); }); } var ticking = false; function prog() { ticking = false; var r = bt.getBoundingClientRect(), p = (innerHeight * 0.6 - r.top) / r.height; fill.style.height = Math.max(0, Math.min(1, p)) * 100 + '%'; } function req() { if (!ticking) { ticking = true; requestAnimationFrame(prog); } } addEventListener('scroll', req, { passive: true }); addEventListener('resize', req); /* First measurement runs from an IntersectionObserver callback (layout already done), not at load. */ if ('IntersectionObserver' in window) { var po = new IntersectionObserver(function (es) { if (es[0].isIntersecting) { prog(); po.disconnect(); } }); po.observe(bt); } else { req(); } })(); /* 8. Hero: NOVA in nine Indian languages (decorative; the static English H1 carries SEO and screen readers). Spellings are transliterations and MUST be checked by a native speaker of each language before launch. */ (function () { var W = $('rotW'), L = $('rotL'); if (!W || !L || reduceMotion) return; var R = [ ['NOVA', 'en', 'English', '#ff6b00', "'Inter'"], ['नोवा', 'hi', 'Hindi', '#0f1fa8', "'Noto Sans Devanagari'"], ['நோவா', 'ta', 'Tamil', '#0b6623', "'Noto Sans Tamil'"], ['నోవా', 'te', 'Telugu', '#a0195b', "'Noto Sans Telugu'"], ['ನೋವಾ', 'kn', 'Kannada', '#6a1b9a', "'Noto Sans Kannada'"], ['നോവാ', 'ml', 'Malayalam', '#b71c1c', "'Noto Sans Malayalam'"], ['নোভা', 'bn', 'Bengali', '#00695c', "'Noto Sans Bengali'"], ['नोव्हा', 'mr', 'Marathi', '#005f9e', "'Noto Sans Devanagari'"], ['નોવા', 'gu', 'Gujarati', '#8a3b00', "'Noto Sans Gujarati'"], ['ਨੋਵਾ', 'pa', 'Punjabi', '#37474f', "'Noto Sans Gurmukhi'"] ]; var i = 0; setInterval(function () { if (document.hidden) return; W.classList.add('out'); setTimeout(function () { i = (i + 1) % R.length; var r = R[i]; W.textContent = r[0]; W.lang = r[1]; W.style.color = r[3]; W.style.fontFamily = r[4] + ",'Noto Sans',system-ui,sans-serif"; L.textContent = r[2]; W.classList.remove('out'); }, 350); }, 2600); })(); /* 9. Callback form: Indian mobile validation + Cloudflare Turnstile */ (function () { var f = $('cbForm'); if (!f) return; var msg = $('cbMsg'), btn = f.querySelector('button[type=submit]'); var mob = f.elements.mobile, hint = $('cbMobHint'); var q = new URLSearchParams(location.search); ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'].forEach(function (k) { if (f.elements[k] && q.get(k)) f.elements[k].value = q.get(k).slice(0, 100); }); f.elements.ts.value = Date.now(); function say(text, cls) { msg.textContent = text; msg.className = cls || ''; } /* Mobile: digits only; +91 / 91 / leading 0 are accepted and removed; then exactly 10 digits, first digit 6-9 */ function normMobile(raw) { var d = String(raw || '').replace(/\D/g, ''); if (d.length === 12 && d.indexOf('91') === 0) d = d.slice(2); else if (d.length === 11 && d.charAt(0) === '0') d = d.slice(1); return d; } function mobileProblem(d) { if (!d) return ''; if (!/^[6-9]/.test(d)) return 'Indian mobile numbers start with 6, 7, 8 or 9.'; if (d.length > 10) return 'Too long. Enter exactly 10 digits (without +91).'; if (d.length < 10) return d.length + ' of 10 digits.'; return ''; } function showHint(final) { var d = normMobile(mob.value), p = mobileProblem(d); var bad = !!p && (final || /^[^6-9]/.test(d) || d.length > 10); if (hint) { hint.textContent = p ? (bad || d.length ? p : '') : (d ? 'Looks good.' : ''); hint.className = 'hint' + (p ? (bad ? ' err' : '') : (d ? ' ok' : '')); } mob.setAttribute('aria-invalid', bad ? 'true' : 'false'); } mob.addEventListener('input', function () { var v = mob.value.replace(/[^\d+\s-]/g, ''); if (v !== mob.value) mob.value = v; showHint(false); }); mob.addEventListener('paste', function () { setTimeout(function () { var d = normMobile(mob.value); if (d) mob.value = d; showHint(false); }, 0); }); mob.addEventListener('blur', function () { var d = normMobile(mob.value); if (d) mob.value = d; showHint(true); }); /* Cloudflare Turnstile: loaded only when configured, and only when the form is near the screen */ var siteKey = (CFG.turnstile || '').trim(), capId = null, capReady = false; function loadCaptcha() { if (!siteKey || capReady || window.__novaTsLoading) return; window.__novaTsLoading = 1; window.novaTsOnload = function () { try { capId = window.turnstile.render('#cbCaptcha', { sitekey: siteKey, theme: 'light', size: 'flexible' }); capReady = true; } catch (e) {} }; var s = document.createElement('script'); s.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit&onload=novaTsOnload'; s.async = true; s.defer = true; document.head.appendChild(s); } if (siteKey) { if ('IntersectionObserver' in window) { var cio = new IntersectionObserver(function (es) { if (es.some(function (e) { return e.isIntersecting; })) { loadCaptcha(); cio.disconnect(); } }, { rootMargin: '400px' }); cio.observe(f); } else { loadCaptcha(); } f.addEventListener('focusin', loadCaptcha); } function captchaToken() { try { return capReady && window.turnstile ? (window.turnstile.getResponse(capId) || '') : ''; } catch (e) { return ''; } } function captchaReset() { try { if (capReady && window.turnstile) window.turnstile.reset(capId); } catch (e) {} } f.addEventListener('submit', function (e) { e.preventDefault(); var mobile = normMobile(mob.value); if (f.elements.name.value.trim().length < 2) { say('Please enter your name.', 'err'); f.elements.name.focus(); return; } if (mobileProblem(mobile) || mobile.length !== 10) { showHint(true); say('Please enter a valid 10-digit Indian mobile number starting with 6, 7, 8 or 9.', 'err'); mob.focus(); return; } if (!f.elements.consent.checked) { say('Please tick the consent box so we can call you.', 'err'); f.elements.consent.focus(); return; } var token = captchaToken(); if (!siteKey) { say('The callback form is not available right now. Please call 044-45630555.', 'err'); return; } if (!token) { say(capReady ? 'Please complete the "I am human" check.' : 'Security check is still loading. Please wait a moment and try again.', 'err'); return; } mob.value = mobile; btn.disabled = true; say('Sending…'); var fd = new FormData(f); fd.set('cf-turnstile-response', token); fetch(CFG.formEndpoint || f.action, { method: 'POST', body: fd, headers: { Accept: 'application/json' } }) .then(function (r) { return r.json().then(function (j) { return { ok: r.ok && j && j.ok, j: j }; }); }) .then(function (r) { if (r.ok) { say('Thank you. Our team will call you back soon.', 'ok'); f.reset(); f.elements.ts.value = Date.now(); if (hint) { hint.textContent = ''; hint.className = 'hint'; } mob.setAttribute('aria-invalid', 'false'); track('generate_lead', { method: 'callback_form' }); } else { var er = r.j && r.j.error; say(er === 'rate' ? 'Too many requests. Please try again later or call 044-45630555.' : er === 'captcha' ? 'The security check failed. Please tick the box again and resend.' : er === 'mobile' ? 'Please enter a valid 10-digit Indian mobile number starting with 6, 7, 8 or 9.' : 'Sorry, we could not send this. Please call 044-45630555.', 'err'); } }) .catch(function () { say('Network problem. Please try again or call 044-45630555.', 'err'); }) .then(function () { btn.disabled = false; captchaReset(); }); }); })(); /* 10. Nova advantage orbit */ (function () { var orbit = $('nvOrbit'), stage = $('nvStage'); if (!orbit || !stage) return; var ic = function (p) { return '' + p + ''; }; var F = [ { cat: 'Charting', t: 'TradingView Charts', ic: ic(''), d: 'Trade straight from professional TradingView charts, with indicators, drawing tools and multiple timeframes.', b: ['Indicators and drawing tools built in', 'Switch between multiple timeframes', 'Place orders directly from the chart'] }, { cat: 'Options', t: 'Strategy Builder', ic: ic(''), d: 'Build multi-leg option strategies and see the payoff before you place a single order. Powered by NxtOption.', b: ['Combine multiple legs in one strategy', 'See the payoff before you trade', 'Powered by NxtOption'] }, { cat: 'Option chain', t: 'Live Option Chain', ic: ic(''), d: 'Scan strikes, premiums and open interest on one screen and move straight to order entry.', b: ['Strikes, premiums and OI together', 'No switching between screens', 'Go from chain to order in one step'] }, { cat: 'Volatility', t: 'IV Analysis', ic: ic(''), d: 'Track implied volatility to judge whether option premiums are rich or cheap before you buy or sell.', b: ['Track implied volatility', 'Judge if premiums are rich or cheap', 'Decide before you buy or sell'] }, { cat: 'Open interest', t: 'OI Analysis', ic: ic(''), d: 'Long build-up, long unwinding, short build-up and short covering — for futures and options.', b: ['Long build-up and long unwinding', 'Short build-up and short covering', 'Covers both futures and options'] }, { cat: 'Orders', t: 'GTT & Basket Orders', ic: ic(''), d: 'Set targets and stop-losses that stay live until triggered, or fire several orders together in one tap.', b: ['Targets and stop-losses stay live until triggered', 'Fire several orders together in one tap', 'Less screen time, more control'] }, { cat: 'Security', t: 'Secure & In Control', ic: ic(''), d: 'TOTP two-factor login, back-office reports inside the app, and one-tap switching between your accounts.', b: ['TOTP two-factor login', 'Back-office reports inside the app', 'One-tap switching between accounts'] }, { cat: 'Investing', t: 'Stock SIP', ic: ic(''), d: 'Build long-term positions by buying your chosen stocks automatically on a fixed schedule.', b: ['Pick the stocks you want to accumulate', 'Buys happen automatically on a fixed schedule', 'Build long-term positions steadily'] } ]; var P = [[32.1, 7.2], [76.7, 11], [39.3, 33.9], [78.4, 43.8], [13.4, 59.3], [49.5, 65.2], [38.4, 94.2], [86.9, 74.8]]; var cards = [], cur = -1; var tickSvg = ''; F.forEach(function (f, i) { var b = document.createElement('button'); b.type = 'button'; b.className = 'nv-card'; b.setAttribute('aria-pressed', 'false'); b.style.left = P[i][0] + '%'; b.style.top = P[i][1] + '%'; b.style.setProperty('--d', (i * -0.9) + 's'); b.innerHTML = '' + f.cat + '' + f.ic + '' + f.t.replace('&', '&') + ''; b.addEventListener('click', function () { show(i); }); orbit.appendChild(b); cards.push(b); }); function show(i, quiet) { cur = (i + F.length) % F.length; var f = F[cur]; $('pCat').textContent = f.cat; $('pIco').innerHTML = f.ic; $('pTitle').textContent = f.t; $('pDesc').textContent = f.d; $('pList').innerHTML = f.b.map(function (x) { return '

  • ' + tickSvg + '' + x + '
  • '; }).join(''); cards.forEach(function (c, k) { c.classList.toggle('on', k === cur); c.setAttribute('aria-pressed', k === cur); }); stage.classList.add('split'); if (!quiet && innerWidth <= 900) document.querySelector('.nv-panel').scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } function close() { stage.classList.remove('split'); cur = -1; cards.forEach(function (c) { c.classList.remove('on'); c.setAttribute('aria-pressed', 'false'); }); } $('pNext').addEventListener('click', function () { show(cur + 1); }); $('pPrev').addEventListener('click', function () { show(cur - 1); }); $('pClose').addEventListener('click', close); document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && cur > -1) close(); }); show(0, true); /* quiet: do not scroll the page on load (original scrolled mobile visitors past the hero) */ })(); })();

    <?php /* * Nova callback form handler (HostGator / PHP). * Emails the request to $TO. Nothing is stored on the server, so no client * data sits in a web-accessible folder. * * BEFORE GO-LIVE: * 1. Confirm $TO (who receives leads). * 2. Make $FROM a real mailbox on maitrawealth.com (create it in cPanel > Email Accounts) * so SPF/DKIM pass and mail does not land in spam. * 3. Cloudflare Turnstile: paste your SECRET key into $TURNSTILE_SECRET. The public SITE key * goes in NOVA_CONFIG.turnstile in index.html, never here. Never put the secret in the web * page. If the secret is empty the form refuses every submission (fails closed). */ $TO = 'info@maitrawealth.com'; $FROM = 'noreply@maitrawealth.com'; $HOST = 'nova.maitrawealth.com'; $LIMIT_PER_HOUR = 5; $TURNSTILE_SECRET = '0x4AAAAAAFJ7Wty62DrzxAnVsBfkSYdco-8'; // Cloudflare Turnstile SECRET key (server only) $TURNSTILE_VERIFY_URL = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'; header('Content-Type: application/json; charset=utf-8'); header('X-Content-Type-Options: nosniff'); header('Cache-Control: no-store'); function out($ok, $error = null, $code = 200) { http_response_code($code); echo json_encode($error ? ['ok' => $ok, 'error' => $error] : ['ok' => $ok]); exit; } function clean($v, $max) { $v = is_string($v) ? $v : ''; $v = preg_replace('/[\x00-\x1F\x7F]+/u', ' ', $v); $v = trim($v); return mb_substr($v, 0, $max, 'UTF-8'); } // Visitor IP. Behind Cloudflare the real visitor IP is in CF-Connecting-IP (REMOTE_ADDR is Cloudflare's). function client_ip() { foreach (['HTTP_CF_CONNECTING_IP', 'REMOTE_ADDR'] as $k) { $v = $_SERVER[$k] ?? ''; if ($v !== '' && filter_var($v, FILTER_VALIDATE_IP)) return $v; } return '0'; } if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') out(false, 'method', 405); // Same-site requests only $src = $_SERVER['HTTP_ORIGIN'] ?? ($_SERVER['HTTP_REFERER'] ?? ''); if ($src !== '' && !in_array(parse_url($src, PHP_URL_HOST), [$HOST, 'www.' . $HOST], true)) out(false, 'origin', 403); // Honeypot: bots fill it. Pretend success. if (!empty($_POST['website'])) out(true); // Too-fast submissions (under 3 seconds after page load) are treated as bots $ts = (int)($_POST['ts'] ?? 0); if ($ts > 0 && (time() * 1000 - $ts) < 3000) out(false, 'fast', 400); // Validate $name = clean($_POST['name'] ?? '', 80); $mobile = preg_replace('/\D/', '', $_POST['mobile'] ?? ''); if (strlen($mobile) === 12 && strpos($mobile, '91') === 0) $mobile = substr($mobile, 2); elseif (strlen($mobile) === 11 && $mobile[0] === '0') $mobile = substr($mobile, 1); $langs = ['English','Hindi','Tamil','Telugu','Kannada','Malayalam','Bengali','Marathi','Gujarati','Punjabi']; $interests= ['Not sure yet','Equity trading','Futures & options','Commodities (MCX)','Mutual funds, SIPs & bonds']; $lang = in_array($_POST['lang'] ?? '', $langs, true) ? $_POST['lang'] : 'English'; $interest = in_array($_POST['interest'] ?? '', $interests, true) ? $_POST['interest'] : 'Not sure yet'; if (mb_strlen($name, 'UTF-8') < 2 || preg_match('/[<>]/', $name)) out(false, 'name', 400); if (!preg_match('/^[6-9][0-9]{9}$/', $mobile)) out(false, 'mobile', 400); if (($_POST['consent'] ?? '') !== 'yes') out(false, 'consent', 400); // Cloudflare Turnstile (fails closed: not configured, missing token, network error or invalid token = refuse) function ts_verify($url, $secret, $token, $ip) { $post = http_build_query(['secret' => $secret, 'response' => $token, 'remoteip' => $ip]); $res = false; $why = ''; if (function_exists('curl_init')) { $ch = curl_init($url); curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => $post, CURLOPT_RETURNTRANSFER => true, CURLOPT_CONNECTTIMEOUT => 4, CURLOPT_TIMEOUT => 6, CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded']]); $res = curl_exec($ch); if ($res === false) $why = 'curl-' . curl_errno($ch) . ': ' . curl_error($ch); else { $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($code !== 200) $why = 'http-' . $code; } curl_close($ch); } else { $ctx = stream_context_create(['http' => ['method' => 'POST', 'timeout' => 6, 'header' => "Content-Type: application/x-www-form-urlencoded\r\n", 'content' => $post]]); $res = @file_get_contents($url, false, $ctx); if ($res === false) $why = 'no-curl-and-fopen-failed'; } if (!is_string($res) || $res === '') return [false, 'unreachable: ' . ($why ?: 'empty response')]; $j = json_decode($res, true); if (!is_array($j)) return [false, 'bad-response' . ($why ? ' (' . $why . ')' : '')]; if (($j['success'] ?? false) === true) return [true, '']; $codes = isset($j['error-codes']) && is_array($j['error-codes']) ? implode(',', array_map('strval', $j['error-codes'])) : 'unknown'; return [false, $codes]; } if ($TURNSTILE_SECRET === '') out(false, 'captcha_config', 500); $token = $_POST['cf-turnstile-response'] ?? ''; if (!is_string($token) || $token === '' || strlen($token) > 2048) out(false, 'captcha', 400); list($tsOk, $tsWhy) = ts_verify($TURNSTILE_VERIFY_URL, $TURNSTILE_SECRET, $token, client_ip()); if (!$tsOk) { error_log('nova callback: Turnstile rejected: ' . $tsWhy); // $tsWhy is only a Cloudflare error code or a connection error: safe to show while setting up. Remove this line once it works. http_response_code(400); // echo json_encode(['ok' => false, 'error' => 'captcha', 'detail' => $tsWhy]); echo json_encode(['ok' => false, 'error' => 'captcha']); exit; } // Rate limit per IP (stores only a salted hash, never the IP or any lead data) $ip = client_ip(); $key = sys_get_temp_dir() . '/nova_rl_' . hash('sha256', $ip . __FILE__); $now = time(); $hits = []; if (is_file($key)) { $hits = array_filter(array_map('intval', explode(',', (string)@file_get_contents($key))), function ($t) use ($now) { return $now - $t < 3600; }); } if (count($hits) >= $LIMIT_PER_HOUR) out(false, 'rate', 429); $hits[] = $now; @file_put_contents($key, implode(',', $hits), LOCK_EX); // Compose (subject and headers are constants: no user input, so no header injection) $utm = []; foreach (['utm_source','utm_medium','utm_campaign','utm_content','utm_term'] as $k) { $v = clean($_POST[$k] ?? '', 100); if ($v !== '') $utm[] = "$k: $v"; } $body = "New Nova callback request\n" . "-------------------------\n" . "Name: $name\n" . "Mobile: +91$mobile\n" . "Preferred language: $lang\n" . "Interested in: $interest\n" . "Consent to be contacted: yes\n" . "Received: " . date('Y-m-d H:i:s') . " (server time)\n" . ($utm ? "Source: " . implode(', ', $utm) . "\n" : ''); $headers = "From: Nova Website <$FROM>\r\nContent-Type: text/plain; charset=UTF-8\r\nX-Mailer: nova-callback"; $sent = @mail($TO, '=?UTF-8?B?' . base64_encode('Nova callback request') . '?=', $body, $headers, '-f' . $FROM); if (!$sent) out(false, 'mail', 500); out(true);

      sai | Maitra Wealth | Maitra Wealth