Open Collections
Payload & Signatures
What we POST to your endpoint.
Event
One event: transaction.forwarded. Fires for every payment a rule on your endpoint claims.
Payload
JSON
{
"event": "transaction.forwarded",
"webhook_id": "ocl_a1b2c3d4e5f6g7h8i9j0k1l2",
"timestamp": "2026-04-29T12:05:32Z",
"data": {
"transaction_id": "550e8400-e29b-41d4-a716-446655440000",
"telco_transaction_id": "73012849466",
"provider": "mtn",
"amount": "150.00",
"currency": "GHS",
"reference": "ACCT-44821",
"payer_phone": "0244123456",
"payer_name": "JOHN DOE",
"transaction_timestamp": "2026-04-29T12:05:30Z",
"received_on": {
"phone_number": "0201234567",
"provider": "MTN MoMo",
"account_name": "Adom Ventures",
"merchant_code": "554120"
},
"hpn_matched": false
}
}| Field | Type | Description |
|---|---|---|
event | string | Always transaction.forwarded |
webhook_id | string | Stable id for this delivery, prefix ocl_. Your idempotency key |
timestamp | string | ISO 8601, UTC |
data.transaction_id | string | Harpoon’s id for the payment |
data.telco_transaction_id | string | null | Provider transaction ID |
data.provider | string | mtn, telecel, or at |
data.amount | string | Fixed-decimal string |
data.currency | string | Always GHS |
data.reference | string | null | The reference the payer typed |
data.payer_phone | string | null | Display format 0XXXXXXXXX |
data.payer_name | string | null | As reported by the provider |
data.transaction_timestamp | string | ISO 8601, UTC |
data.received_on | object | null | Which of your numbers took the money |
data.received_on.phone_number | string | Display format 0XXXXXXXXX |
data.received_on.provider | string | MTN MoMo, Telecel Cash, or AT Money |
data.received_on.account_name | string | The name on this number |
data.received_on.merchant_code | string | null | The till on this number |
data.hpn_matched | boolean | A Harpoon payment code already matched it. Only true on a rule with include_matched |
Headers
| Header | Value |
|---|---|
X-Harpoon-Signature | sha256=<hex> |
X-Harpoon-Signature-Previous | For 24 hours after a rotation, signed with the old secret |
X-Harpoon-Timestamp | Unix timestamp |
X-Harpoon-Webhook-ID | Same as webhook_id in body |
Content-Type | application/json |
User-Agent | Harpoon-Webhook/1.0 |
Verify the signature
Same HMAC-SHA256 scheme as Harpoon webhooks, using the secret from endpoint creation.
For 24 hours after a rotation both signatures are sent: the new one in X-Harpoon-Signature, the old in X-Harpoon-Signature-Previous. Accept either while you change over.
Idempotency
Use webhook_id as your idempotency key. Retries reuse it, and one payment reaches an endpoint once, so transaction_id never arrives twice at the same URL. Resending and replaying reuse the original delivery.
Two endpoints claiming one payment each get their own webhook_id for the same transaction_id. That is one payment reaching two systems, not a duplicate.