Skip to main content

Overview

Webhooks let your application receive real-time notifications when events occur — a card charge completes, a recurring payment fails, an invoice is paid. Instead of polling the API, Dime Payments pushes event data to a URL you specify.

Setting up a webhook

  1. Log into the Dime Payments dashboard
  2. Navigate to Advanced > Webhooks
  3. Click Add Endpoint
  4. Enter your publicly accessible HTTPS URL
  5. Select the event this endpoint should receive
  6. Save
Your endpoint must return a 200 response within 15 seconds or the attempt is considered failed. Do any real work asynchronously — acknowledge the delivery first, then process it on a queue.

One endpoint per event

Dime Payments registers each webhook against a single event. Payloads do not carry an event-name field, so you cannot inspect the body to tell which event fired. Instead, register a separate endpoint URL for each event you care about and identify the event by which URL received the delivery. For example, point Credit Card Charge at https://yourapp.com/hooks/card-charge and Invoice Paid at https://yourapp.com/hooks/invoice-paid, then handle each route separately.

Event types

These are the events you can subscribe to, grouped the way they appear in the dashboard. Each name is the exact string shown when you add an endpoint. Every group has a Select all option.

Onboarding

Credit card

Chargebacks

Risk retrievals

Chargeback and retrieval data comes from the processor in a once-daily file, so these events fire after that import, not the moment the bank acts. Send evidence for either one through Upload Documents with doc_type set to RetrievalRequest.
Credit Card Chargeback still fires for existing subscriptions, alongside the Chargebacks events. It only covers a chargeback being filed. The Chargebacks events also tell you when one changes or is decided.

ACH

Recurring payments

Invoicing

Subscriptions

Held funds

For merchants on a held funds setup. A release sends one of these as soon as its outcome is saved.

Payload format

The request body is a flat JSON object. The event data is sent at the top level — there is no envelope wrapping it. A transaction webhook:
An invoice webhook:
Every payload carries company_sid, the merchant the event belongs to. Invoice, recurring invoice, subscription, recurring payment and held funds payloads also carry type, the event in snake_case (invoice_paid, invoice_payment_returned, funds_released, …). Transaction, ACH and deposit payloads do not.

Application Status Changed

Fires every time a merchant moves to a new onboarding stage. The payload carries the same fields as Get Application Status: When status is underwriting, read application_status. Only needs_documents means you need to do something. And use boarded as the signal that the merchant is ready to take payments: a merchant can be live without application_status being approved. Subscribe to this rather than polling the endpoint, and use the endpoint only to reconcile after an outage.

Invoice Payment Returned

An ACH payment can be returned by the customer’s bank days after it was made, for example for insufficient funds or a closed account. When that happens to a payment on an invoice, the invoice is reopened before this event is sent: the payment is taken off it, and status, amount_paid and balance show what is owed now. The status goes back to partially_paid if other payments still stand, otherwise to viewed or sent. The customer is emailed a link to pay again. The payload is the invoice, with a returned_payment block added:
(The rest of the invoice fields are shown above and are left out here.) Returns come in with the nightly bank-return file, usually two to five banking days after the payment, so expect this event the morning after a return rather than in real time. The same return also sends ACH Payment Return. That older event doesn’t say which invoice it belongs to, so build against this one.
If your system treats Invoice Paid as final, handle this event too: a paid invoice can become unpaid again. Void and refunded invoices are never reopened.

Subscription Payment Failed

This event fires in two cases. failure_source tells them apart:
  • charge: the renewal charge was declined.
  • ach_return: an ACH payment had gone through (a renewal, or the subscription’s first payment), and the customer’s bank returned it days later, for example for insufficient funds or a closed account. The payload adds a returned_payment block, the same shape as on Invoice Payment Returned.
A returned payment is handled like a declined renewal, and the subscription has already been updated when the event is sent:
  • Insufficient funds: the payment is retried the next day, and counts toward the three failed attempts after which the subscription’s status becomes Failed.
  • Closed or invalid account: the subscription is Failed at once. The customer is emailed a link to update their bank details.
  • Payment stopped or authorization revoked: the subscription is Cancelled, and Subscription Cancelled is also sent.
The customer still owes the returned period; the retry collects it. If the subscription was already paused, cancelled, ended or failed, it’s left as it is, but the event is still sent.
If your system treats Subscription Renewed as final, handle this event too: a renewal that succeeded can be returned by the bank two to five banking days later. Payloads sent before failure_source existed are always declined charges.

Held funds releases

Funds Released, Funds Release Failed and Funds Release Unconfirmed carry the release exactly as POST /api/funds/release returns it, plus company_sid and type. You can reconcile the webhook and the API response the same way.
transaction_info_ids lists the payments released when the release named payments, and is null when it named an amount. Match on idempotency_key or id to find the release in your own records. Treat funds_release_unconfirmed as neither sent nor failed. Its amount is held back from releasable until it is reconciled, so it can’t be paid out twice. Don’t mark it failed in your ledger. Check the balance later to see how it resolved. A retry with the same idempotency_key returns the original release and sends no new webhook. When the merchant belongs to an affiliate, the affiliate’s endpoints for these events receive them too. company_sid tells the merchants apart.
For the payloads that carry no type, identify the event from the endpoint URL that received it (see One endpoint per event above).

Verifying webhook signatures

Dime Payments signs every delivery with the secret shown for that endpoint in the dashboard. Each request carries three headers: Verify X-Dime-Signature. The timestamp is part of what is signed, so it can’t be changed without breaking the signature. Rejecting old timestamps stops anyone who captures a delivery from replaying it to you later.
  • Sign the raw body, not a re-encoding of the parsed JSON. Re-encoding can reorder keys and reformat numbers, which causes mismatches that look random.
  • Compare with a timing-safe function such as hash_equals, never ==.
  • Five minutes (300 seconds) is a sensible tolerance. Choose your own, but allow for clock differences in both directions.
  • The timestamp is when that attempt was sent, not when the event happened. Every retry is signed again with a fresh timestamp, so a retry hours later still passes your tolerance. That also means the timestamp can’t tell you about duplicates: a retry of an event you already handled looks new. Deduplicate on an id in the payload, such as a transaction, invoice, chargeback or release id.
Signature still works exactly as before, so an integration that already checks it keeps working. It proves the delivery came from us and wasn’t changed, but not when it was sent, so a captured delivery would still verify later. Move to X-Dime-Signature when you can.

Retries

Any response other than a 2xx within 15 seconds counts as a failure. A failed delivery is retried with exponential backoff, up to 5 attempts spread over about three hours (retries after about 10 seconds, 100 seconds, 17 minutes and 3 hours), before it is abandoned. Because a delivery can arrive more than once, make your handler safe to run twice.
After 3 events in a row fail every attempt, the endpoint is disabled and its owner is emailed. Nothing is sent to it until someone turns it back on from the Webhooks screen. Events that happen while it is disabled are not queued and can’t be resent.
There is no delivery-history screen and no manual resend, so treat webhooks as best-effort rather than a guaranteed ledger. Reconcile against the API for anything you can’t afford to miss.

Testing webhooks locally

Use a tool like ngrok to expose your local server to the internet during development:
Use the generated https:// URL as your webhook endpoint in the dashboard.

Next steps

  • Disputes — understand how chargebacks work
  • Held Funds — release held money and track the result
  • Payouts — understand how funds are deposited
  • API Reference — full endpoint documentation