Skip to main content

Overview

Webhooks let Hyparrow push events to your server in real time instead of you polling the API. When something happens, a subscription renewal is paid, a customer transaction completes, Hyparrow sends an HTTP POST with a JSON payload to the URL you configure. Each webhook request carries these headers: A delivery is acknowledged by 200, 201, 202 or 204. Anything else, or no answer within 30 seconds, is retried: the first attempt is immediate, then retries follow after 1m, 5m, 30m, 2h and 24h, for 6 attempts in total over roughly 26.5 hours. After the last one the delivery is marked failed and stays visible in delivery history.
Base URL: https://api.hyparrow.cloud/api/v1Webhook settings endpoints are authenticated with your API key pair:
Point your webhook URL at a sandbox listener and configure it from https://sandbox.hyparrow.cloud/api/v1 with pk_test_ keys to safely exercise deliveries.

Configure your webhook URL

POST /webhooks/settings sets the URL Hyparrow delivers events to. You may also pass an optional allowedIps list to restrict which source IPs your endpoint accepts.
1

Set your URL

POST your HTTPS endpoint to /webhooks/settings.
2

Generate a signing secret

Call /webhooks/settings/secret so deliveries are signed.
3

Verify the signature on every request

Recompute the HMAC over the raw body and compare it to X-Hyparrow-Signature.
4

Send a test event

Use /webhooks/settings/test to confirm end-to-end delivery.
webhookUrl must be a valid URL. An invalid value returns 400.

View current settings

GET /webhooks/settings returns your configured URL, allowed IPs, whether a secret is set (with a masked preview), and your customer-email preference.

Generate a signing secret

POST /webhooks/settings/secret generates a new signing secret and stores it. The full secret is returned only once, store it securely.
Generating a new secret replaces any previous one. Update your verification code immediately so you don’t reject incoming deliveries.

Verifying the signature

When a secret is set, every delivery includes an X-Hyparrow-Signature header. It is an HMAC-SHA512 of the raw request body, keyed with your webhook secret and hex-encoded. To verify, recompute the HMAC over the exact bytes you received and compare it to the header using a constant-time comparison. Reject the request if they differ.
Compute the HMAC over the raw, unparsed body. Re-serializing the parsed JSON can change byte ordering or whitespace and produce a different signature.
X-Hyparrow-Signature covers only the body, so a captured delivery would still verify if someone replayed it later. X-Hyparrow-Signature-256 closes that gap. It is the lowercase hex HMAC-SHA256, keyed with the same webhook secret, of the string:
To verify:
  1. Read X-Hyparrow-Timestamp and reject the request if it is more than 5 minutes from your clock. Every attempt, including a retry, is signed when it is sent, so a genuine delivery is always fresh.
  2. Compute the HMAC-SHA256 over timestamp + "." + rawBody and compare it to X-Hyparrow-Signature-256 in constant time.
  3. Deduplicate on X-Webhook-ID, which stays the same across retries of one event.
Both headers are sent on every signed delivery, so an integration already verifying X-Hyparrow-Signature keeps working unchanged.

Send a test webhook

POST /webhooks/settings/test delivers a sample event to your configured URL so you can validate your endpoint and signature handling.
The payload your endpoint receives looks like:
If no webhook URL is configured, this returns 400 No webhook URL configured.

Example event payload

All events share the same envelope: a top-level event name, a timestamp, and a data object. On transfer, subscription and test events timestamp is Unix seconds (a number); on payroll events it is an RFC 3339 string. For a single clock to check against, use the X-Hyparrow-Timestamp header, which is Unix seconds on every event. Here is a subscription.payment.completed event, delivered when a subscription renewal is paid:
Other events you may receive include checkout.payment.completed (a checkout-link payment landed) and customer.transaction.completed (a customer transaction settled). All follow the same event / timestamp / data envelope, so write your handler to switch on the event field.

Payroll

Bulk salary runs emit four events. Item events carry both the item’s own transferCode and the batchReference, so you can reconcile per employee or per run without keeping your own mapping between the two. See Payroll.
As with a single transfer, an item reaching sent is not a confirmed credit — that is the local debit record. payroll.item.settled is what confirms delivery.

Outbound transfer settlement

transfer.settled and transfer.failed tell you the final outcome of a payout you initiated with POST /money-transfer/send. They fire only once the outcome is known for certain. transfer.reversed tells you money for a transfer was credited back to your wallet. Hyparrow asks the processor for the outcome of recent transfers every 5 minutes, starting 5 minutes after the send, so a settled or failed event normally arrives within about 10 minutes. A transfer the processor cannot see yet is asked about again after 15 minutes, 1 hour, 6 hours and then daily. This matters because the status returned by POST /money-transfer is written when your wallet is debited, which happens before the transfer is submitted downstream. It confirms the debit, not the delivery. These events are what confirm delivery.
The provider object is the upstream processor’s own record, passed through unmodified so you can verify the outcome independently rather than taking our word for it:
  • transactionResponseCode — 90000 means the beneficiary was credited. Any other value on a transfer.failed event is the decline reason (for example 90051, insufficient funds).
  • transactionRef — the bank-traceable reference. Quote this when querying a beneficiary bank.
  • transactionId — the processor’s record id.
amount is the principal and fee is the fee charged on top of it, both in kobo, so each can be posted as its own ledger entry.
Set clientReference on your POST /money-transfer/send request and it is echoed back on these events, so you can match a settlement to your own record without storing the transferCode.

Reversals

transfer.reversed fires whenever money for a transfer comes back to your wallet:
  • At submission. If the processor refuses a transfer outright, the send returns an error, the principal is credited straight back and no fee is charged. transfer.reversed confirms the credit with reason: "provider_rejected".
  • After a decline. A transfer.failed debit is reviewed and then reversed, principal and fee. transfer.reversed fires when that happens, with reason: "manual_reversal". Until then the debit stands; contact support if a failed transfer has not been reversed.
Post totalReturned as the reversing entry against the original clientReference. The reversal also appears in GET /transactions and the statement export as a credit row with reference REV_<transferCode>, carrying the same clientReference.
Payroll items do not use transfer.reversed. Their outcomes come on payroll.item.* events and their money returns through the batch reservation.

Customer email preference

PUT /webhooks/settings/customer-emails controls whether Hyparrow sends transactional emails (invoice receipts, subscription notices) directly to your end customers. Set handleCustomerEmails to false if you prefer to own all customer communication yourself.
The handleCustomerEmails field is required and must be a boolean. Omitting it returns 400; an explicit false is accepted and disables customer-facing emails.

Delivery history

GET /webhooks/events returns recent delivery attempts so you can audit what was sent and whether it succeeded. Useful both for debugging and as a polling fallback.
A delivery’s status is one of pending (awaiting a retry), delivered (a 2xx was returned), or failed (exhausted all attempts). Inspect lastError and httpStatus to diagnose failures.