In a rush? Schedule an Appointment
    Fetching local time...
    Anytime Capital

    Get started

    • Overview
    • Quickstart
    • Pricing
    • Support

    Accept payments

    • Checkout & website
    • In person (QR)
    • Payment methods
    • Fraud rules & 3D Secure

    Get paid out

    • Payout options

    API reference

    • Authentication
    • Errors
    • Pagination
    • Idempotency
    • Rate limits
    • Payment links
    • Invoices
    • Customers
    • Payments
    • Webhooks

    Build with AI

    • MCP server & AI tools

    FAQ

    • FAQ

    For AI tools

    • llms-full.txt
    • MCP server
    Docs menu · Webhooks

    Get started

    • Overview
    • Quickstart
    • Pricing
    • Support

    Accept payments

    • Checkout & website
    • In person (QR)
    • Payment methods
    • Fraud rules & 3D Secure

    Get paid out

    • Payout options

    API reference

    • Authentication
    • Errors
    • Pagination
    • Idempotency
    • Rate limits
    • Payment links
    • Invoices
    • Customers
    • Payments
    • Webhooks

    Build with AI

    • MCP server & AI tools

    FAQ

    • FAQ

    For AI tools

    • llms-full.txt
    • MCP server
    DocsAPI referenceWebhooks

    Webhooks

    Get Anytime Capital Payments events by webhook: payments, refunds, disputes, invoices and subscriptions, signed with HMAC-SHA256 and retried for 24 hours.

    View as MarkdownOpen in Claude

    Add an endpoint in Settings → Developers in your dashboard and we POST a JSON event to it as things happen. Each event wraps the same object the API returns, in data, so one parser handles both.

    An event
    {
      "id": "evt_3f9a1c2b7d4e6f8a0b1c2d3e4f5a6b7c",
      "type": "payment.succeeded",
      "created": "2026-10-14T16:02:16.000Z",
      "data": {
        "object": "payment",
        "id": "pay_7Hq2LmX9cR4tVb",
        "status": "paid",
        "amountCents": 12500,
        "amountRefundedCents": 0,
        "netCents": 12088,
        "currency": "usd",
        "description": "Consulting — October",
        "source": {
          "type": "payment_link",
          "id": "pl_4kTz8QwN2xYb6RcD1mPa"
        },
        "customer": {
          "name": "Jordan Lee",
          "email": "jordan@example.com",
          "country": "US"
        },
        "card": {
          "brand": "visa",
          "last4": "4242"
        },
        "paymentMethod": {
          "type": "card",
          "label": "Visa •••• 4242"
        },
        "clientReference": "order_1042",
        "metadata": {
          "campaign": "fall-launch"
        },
        "amountDetails": {
          "subtotalCents": 12500,
          "discountCents": 0,
          "shippingCents": 0,
          "taxCents": 0,
          "customerFeeCents": 0
        },
        "dispute": null,
        "failureMessage": null,
        "createdAt": "2026-10-14T16:02:11.000Z",
        "paidAt": "2026-10-14T16:02:15.000Z"
      }
    }

    Add an endpoint

    1. In Settings → Developers, choose Add endpoint.
    2. Enter an https:// URL on a public domain name (not an IP address or localhost).
    3. Choose the events to receive, or every event, including types added later.
    4. Copy the endpoint's signing secret (whsec_…), shown when you add the endpoint, into a server-side environment variable such as ANYTIME_WEBHOOK_SECRET. Owners can Reveal it again later.
    5. Choose Send test event to check your endpoint answers 2xx.

    A business can have up to 10 endpoints, each with its own secret and event selection. Owners and admins manage them, and can replace an endpoint's secret at any time: events signed with the old secret stop verifying immediately, so deploy the new one straight away.

    Event types

    Typedata is aWhen
    payment.succeededpaymentA card payment went through. Sent once per payment.
    payment.failedpaymentA card payment was declined or blocked.
    payment.refundedpaymentAll or part of a payment was refunded. Sent for each refund.
    dispute.openedpaymentA customer disputed a payment with their bank.
    dispute.closedpaymentA dispute was decided: won, lost or closed.
    invoice.paidinvoiceAn invoice was paid in full by card.
    subscription.createdsubscriptionA customer subscribed to one of your plans.
    subscription.canceledsubscriptionA subscription ended: canceled, expired or completed.
    webhook.testtestSent by “Send test event”, whatever the endpoint subscribes to.

    Payment and dispute events carry the payment object, invoice.paid the invoice object, and the subscription events a subscription: id, status, plan (id, title, amountCents, currency, interval), customer, currentPeriodEnd, cancelAtPeriodEnd, canceledAt and createdAt.

    Delivery and retries

    • Respond with any 2xx status within 10 seconds to acknowledge an event; do slow work after responding.
    • Anything else (another status, a redirect, a timeout) is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 24 hours, then marked failed.
    • Every attempt and response is in the endpoint's delivery log, where you can Resend any event.
    • If several events in a row fail every retry, we turn the endpoint off and email your owners and admins.
    • Delivery is at least once and events can arrive out of order: use id (also in the Anytime-Event-Id header) to skip events you've handled, and re-read the object from the API when order matters.
    HeaderValue
    Anytime-Signaturet=<unix seconds>,v1=<hex HMAC-SHA256>
    Anytime-Event-IdThe event's id.
    Anytime-Event-TypeThe event's type.
    Content-Typeapplication/json

    Verifying signatures

    Every event carries an Anytime-Signature header such as t=1760630536,v1=5257a869…. v1 is the hex HMAC-SHA256, keyed with your endpoint's signing secret (the whole whsec_… string), of the timestamp, a period and the raw request body. Compute it yourself, compare in constant time, and reject timestamps more than 5 minutes old so a captured request can't be replayed.

    Use the raw body

    The signature covers the exact bytes we sent. Verify before parsing, and don't verify a body your framework has already parsed and re-serialised: whitespace and key order would change.

    Node.js (Express)
    import crypto from "node:crypto";
    import express from "express";
    
    const app = express();
    const secret = process.env.ANYTIME_WEBHOOK_SECRET; // whsec_…
    
    // Read the raw body: the signature covers the exact bytes we sent.
    app.post("/webhooks/anytime", express.raw({ type: "application/json" }), (req, res) => {
      const header = req.get("Anytime-Signature") ?? "";
      const parts = Object.fromEntries(header.split(",").map((part) => part.split("=")));
      const body = req.body.toString("utf8");
    
      const expected = crypto
        .createHmac("sha256", secret)
        .update(`${parts.t}.${body}`)
        .digest("hex");
      const signed =
        typeof parts.v1 === "string" &&
        parts.v1.length === expected.length &&
        crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
      const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300;
      if (!signed || !fresh) return res.status(400).send("Invalid signature");
    
      const event = JSON.parse(body);
      // The same event can arrive more than once: skip ids you've already handled.
      switch (event.type) {
        case "payment.succeeded":
          // Fulfil the order for event.data.id
          break;
        case "payment.refunded":
          // event.data.amountRefundedCents is the total refunded so far
          break;
      }
      res.sendStatus(200);
    });
    Next.js route handler
    // app/api/webhooks/anytime/route.ts (Next.js App Router)
    import crypto from "node:crypto";
    
    export async function POST(request: Request) {
      const secret = process.env.ANYTIME_WEBHOOK_SECRET!; // whsec_…
      const body = await request.text(); // the raw body, exactly as sent
      const header = request.headers.get("Anytime-Signature") ?? "";
      const parts = Object.fromEntries(header.split(",").map((part) => part.split("=")));
    
      const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${body}`).digest("hex");
      const signed =
        typeof parts.v1 === "string" &&
        parts.v1.length === expected.length &&
        crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
      const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300;
      if (!signed || !fresh) return new Response("Invalid signature", { status: 400 });
    
      const event = JSON.parse(body);
      // Skip event.id if you've handled it before, then act on event.type.
      if (event.type === "payment.succeeded") {
        // Mark the order in event.data.metadata (or event.data.clientReference) paid.
      }
      return new Response(null, { status: 200 });
    }
    Python (Flask)
    # Flask
    import hashlib, hmac, json, os, time
    from flask import Flask, abort, request
    
    app = Flask(__name__)
    SECRET = os.environ["ANYTIME_WEBHOOK_SECRET"]  # whsec_…
    
    @app.post("/webhooks/anytime")
    def anytime_webhook():
        body = request.get_data()  # the raw bytes, exactly as sent
        parts = dict(p.split("=", 1) for p in request.headers.get("Anytime-Signature", "").split(",") if "=" in p)
        timestamp = parts.get("t", "")
        expected = hmac.new(SECRET.encode(), timestamp.encode() + b"." + body, hashlib.sha256).hexdigest()
        if not hmac.compare_digest(expected, parts.get("v1", "")):
            abort(400)
        if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > 300:
            abort(400)
    
        event = json.loads(body)
        # Skip event["id"] if you've handled it before, then act on event["type"].
        return "", 200

    Handling events well

    • Record each id you process and skip repeats.
    • Fulfil orders on payment.succeeded, never on the customer reaching your success page: a bank account payment can take days, and a customer can close the tab.
    • Match a payment to your order with data.metadata (from the link) or data.clientReference (from ?ref=).
    • On payment.refunded, data.amountRefundedCents is the total refunded so far, not this refund alone.
    • On dispute.opened, respond from Disputes in the dashboard before the deadline.
    PreviousPaymentsNext MCP server & AI tools

    Anytime Capital is a fintech company, not a bank. Payment processing, banking and payout services are provided by Anytime Capital's licensed partners. Rates shown are for US card payments; international cards, currency conversion and disputes may carry additional fees shown in your dashboard before you accept a payment.

    On this page

    • Add an endpoint
    • Event types
    • Delivery and retries
    • Verifying signatures
    • Handling events well
    Anytime Capital
    (470) 748-2530
    App Store

    Business

    • Business Checking
    • Business Debit Cards
    • Payments
    • Pricing
    • In-Person Payments
    • Invoicing
    • Global Payouts
    • Bill Pay
    • Expense Management
    • Developer Docs

    Solutions

    • CPAs & Accountants
    • Financial Advisors
    • Boat & Auto Dealerships
    • Dental & Medical Practices
    • Creators & Coaches
    • All solutions

    Crypto

    • Crypto Wallet
    • Tether Wallet
    • Buy Digital Assets
    • Explore Markets
    • Security & Licenses
    • Compare Platforms
    • Bitcoin ATM Alternative

    Company

    • Our Story
    • Schedule Appointment
    • Careers
    • Media & Press
    • Investor Relations
    • FAQ

    Branches

    • Atlanta, Georgia
    • Miami, Florida
    • Houston, Texas
    • All branches

    Legal

    • Terms & Conditions
    • Privacy Policy
    • Your Privacy Choices
    • Fraud Notice

    © 2026 Anytime Capital LLC. Anytime Capital is a fintech company, not a bank.

    The Anytime Capital Mastercard® Prepaid Card is issued by Pathward®, N.A., Member FDIC. Mastercard® is a registered trademark of Mastercard International Incorporated. All other trademarks and service marks belong to their respective owners.

    Mastercard